From f39726143e083939fc79053fcabfc72c2c0558ef Mon Sep 17 00:00:00 2001 From: zohar Date: Wed, 15 Feb 2023 17:03:01 +0800 Subject: [PATCH] =?UTF-8?q?[demo/nuttx=5Fspiflash]=20=E6=96=B0=E5=A2=9Enut?= =?UTF-8?q?tx=E5=B9=B3=E5=8F=B0=E7=9A=84demo=EF=BC=8CEasyflash=E6=94=AF?= =?UTF-8?q?=E6=8C=81spi=20flash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- demo/os/nuttx-spiflash/README.md | 89 + .../apps/examples/easylogger/Kconfig | 35 + .../apps/examples/easylogger/Make.defs | 23 + .../apps/examples/easylogger/Makefile | 38 + .../apps/examples/easylogger/elog_main.c | 144 ++ .../apps/system/easyflash/Kconfig | 15 + .../apps/system/easyflash/Make.defs | 23 + .../apps/system/easyflash/Makefile | 33 + .../apps/system/easyflash/inc/easyflash.h | 107 + .../apps/system/easyflash/inc/ef_cfg.h | 79 + .../apps/system/easyflash/inc/ef_def.h | 124 ++ .../system/easyflash/plugins/types/README.md | 152 ++ .../system/easyflash/plugins/types/ef_types.c | 395 ++++ .../system/easyflash/plugins/types/ef_types.h | 76 + .../plugins/types/struct2json/inc/cJSON.h | 154 ++ .../plugins/types/struct2json/inc/s2j.h | 91 + .../plugins/types/struct2json/inc/s2jdef.h | 150 ++ .../plugins/types/struct2json/readme.md | 61 + .../plugins/types/struct2json/src/cJSON.c | 762 +++++++ .../plugins/types/struct2json/src/s2j.c | 52 + .../apps/system/easyflash/port/ef_port.c | 232 +++ .../apps/system/easyflash/src/easyflash.c | 109 + .../apps/system/easyflash/src/ef_env.c | 1841 +++++++++++++++++ .../apps/system/easyflash/src/ef_env_legacy.c | 922 +++++++++ .../system/easyflash/src/ef_env_legacy_wl.c | 1082 ++++++++++ .../apps/system/easyflash/src/ef_iap.c | 289 +++ .../apps/system/easyflash/src/ef_log.c | 731 +++++++ .../apps/system/easyflash/src/ef_utils.c | 99 + .../apps/system/easylogger/Kconfig | 27 + .../apps/system/easylogger/Make.defs | 23 + .../apps/system/easylogger/Makefile | 45 + .../apps/system/easylogger/inc/elog_cfg.h | 86 + .../easylogger/plugins/flash/elog_flash_cfg.h | 37 + .../plugins/flash/elog_flash_port.c | 80 + .../apps/system/easylogger/port/elog_port.c | 168 ++ docs/zh/images/ElogNuttxSpiFlashDemo.png | Bin 0 -> 96352 bytes 37 files changed, 8375 insertions(+), 1 deletion(-) create mode 100644 demo/os/nuttx-spiflash/README.md create mode 100644 demo/os/nuttx-spiflash/apps/examples/easylogger/Kconfig create mode 100644 demo/os/nuttx-spiflash/apps/examples/easylogger/Make.defs create mode 100644 demo/os/nuttx-spiflash/apps/examples/easylogger/Makefile create mode 100644 demo/os/nuttx-spiflash/apps/examples/easylogger/elog_main.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/Kconfig create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/Make.defs create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/Makefile create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/inc/easyflash.h create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/inc/ef_cfg.h create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/inc/ef_def.h create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/README.md create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/ef_types.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/ef_types.h create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/inc/cJSON.h create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/inc/s2j.h create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/inc/s2jdef.h create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/readme.md create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/src/cJSON.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/src/s2j.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/port/ef_port.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/src/easyflash.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_env.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_env_legacy.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_env_legacy_wl.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_iap.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_log.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_utils.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easylogger/Kconfig create mode 100644 demo/os/nuttx-spiflash/apps/system/easylogger/Make.defs create mode 100644 demo/os/nuttx-spiflash/apps/system/easylogger/Makefile create mode 100644 demo/os/nuttx-spiflash/apps/system/easylogger/inc/elog_cfg.h create mode 100644 demo/os/nuttx-spiflash/apps/system/easylogger/plugins/flash/elog_flash_cfg.h create mode 100644 demo/os/nuttx-spiflash/apps/system/easylogger/plugins/flash/elog_flash_port.c create mode 100644 demo/os/nuttx-spiflash/apps/system/easylogger/port/elog_port.c create mode 100644 docs/zh/images/ElogNuttxSpiFlashDemo.png diff --git a/README.md b/README.md index 73499fb..9d56606 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ - 支持用户自定义输出方式(例如:终端、文件、数据库、串口、485、Flash...); - 日志内容可包含级别、时间戳、线程信息、进程信息等; - 日志输出被设计为线程安全的方式,并支持 **异步输出** 及 **缓冲输出** 模式; -- 支持多种操作系统([RT-Thread](http://www.rt-thread.org/)、UCOS、Linux、Windows...),也支持裸机平台; +- 支持多种操作系统([RT-Thread](http://www.rt-thread.org/)、UCOS、Linux、Windows、Nuttx...),也支持裸机平台; - 日志支持 **RAW格式** ,支持 **hexdump** ; - 支持按 **标签** 、 **级别** 、 **关键词** 进行动态过滤; - 各级别日志支持不同颜色显示; diff --git a/demo/os/nuttx-spiflash/README.md b/demo/os/nuttx-spiflash/README.md new file mode 100644 index 0000000..cb57dd5 --- /dev/null +++ b/demo/os/nuttx-spiflash/README.md @@ -0,0 +1,89 @@ +# Nuttx SPI Flash demo + +--- + +## 1 + +ͨ `apps/examples/easylogger/elog_main.c` `test_elog()` ־ĬϿ첽ģʽûԽն־á +`test_env()` ʾĶȡ޸ĹܣÿϵͳҳʼEasyFlashɹø÷ + +- ƽ̨NuttX-12.0.0NuttX-10.X.XҪ޸Makefileinclude÷ +- ӲSTM32F103NuttxappsӲƽ̨ +- FlashW25QXXNuttxֵ֧SPI Flash`nuttx/drivers/mtd`ļ + +## 2ʹ÷ + +ɺnsh̨`elog`سű۲ + +![ElogNuttxSpiFlashDemo](https://raw.githubusercontent.com/armink/EasyLogger/master/docs/zh/images/ElogNuttxSpiFlashDemo.png) + +## 3ļУ˵ + +|ԴļУ | | +|:------------------------------ |:----- | +|apps |nuttx-appsӦòĿ¼| +|apps/examples/ |nuttxӦòʾĿ¼| +|apps/examples/easylogger/elog_main.c |EasyloggerEasyflashExample Demo| +|apps/system/ |nuttxӦòϵͳĿ¼| +|apps/system/easylogger/inc/elog_cfg.h |Easyloggerļ| +|apps/system/easylogger/port/elog_port.c |Easyloggerֲοļ| +|apps/system/easyflash/inc/ef_cfg.h |Easyflashļ| +|apps/system/easyflash/port/ef_port.c |Easyflashֲοļ| + +## 4˵ + +### 4.1ֲ˵ + +1ѸĿ¼`EasyLogger/`Щļָλã +``` +cd EasyLogger/ +cp easylogger/inc/elog.h demo/os/nuttx-spiflash/apps/system/easylogger/inc +cp easylogger/plugins/flash/elog_flash.* demo/os/nuttx-spiflash/apps/system/easylogger/plugins/flash/ +cp -R easylogger/src/ demo/os/nuttx-spiflash/apps/system/easylogger/ +``` + +2appsĿ¼ǵnuttxappsĿ¼¡ + +3nuttxĿ¼appsĿ¼ṹ档 +``` +make apps_distclean +make menuconfig +``` + +4`make menuconfig`ѡãEasylogger Demo Example +``` +CONFIG_EXAMPLES_EASYLOGGER=y +``` +Զ +``` +CONFIG_MTD_W25=y +``` + +5mtd ioctlָIDرEasyflashܿʡԣ +- `nuttx/include/nuttx/mtd/mtd.h`ļӣ +```C +#define MTDIOC_GETMTDDEV _MTDIOC(0x000c) +``` + +6¶w25mtd豸ڵ㣺رEasyflashܿʡԣ +- `nuttx/drivers/mtd/w25.c`ļ +- ӱ +```C +static FAR struct mtd_dev_s *mtd_w25; +``` +- ҵ`w25_ioctl``switch (cmd)`ӣ +```C + case MTDIOC_GETMTDDEV:{ + FAR struct mtd_dev_s **mtd = + (FAR struct mtd_dev_s *)((uintptr_t)arg); + DEBUGASSERT(*mtd != NULL); + *mtd = mtd_w25; + ret = OK; + }break; +``` +- ҵ`w25_initialize``ret = w25_readid(priv)`IDȷصӣ +```C +mtd_w25=&priv->mtd; +``` + +7`make -j4`صӺNSH̨`elog`ɲ鿴 diff --git a/demo/os/nuttx-spiflash/apps/examples/easylogger/Kconfig b/demo/os/nuttx-spiflash/apps/examples/easylogger/Kconfig new file mode 100644 index 0000000..e8cf6c6 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/examples/easylogger/Kconfig @@ -0,0 +1,35 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config EXAMPLES_EASYLOGGER + tristate "Easylogger and EasyFlash Demo" + default n + select SYSTEM_EASYLOGGER + select SYSTEM_EASYLOGGER_FLASH + select SYSTEM_EASYFLASH + select CONFIG_MTD + select CONFIG_MTD_BYTE_WRITE + select CONFIG_MTD_W25 + ---help--- + Enable the Easylogger Demo + +if EXAMPLES_EASYLOGGER + +config EXAMPLES_EASYLOGGER_PROGNAME + string "Program name" + default "elog" + ---help--- + This is the name of the program that will be used when the NSH ELF + program is installed. + +config EXAMPLES_EASYLOGGER_PRIORITY + int "Easylogger task priority" + default 100 + +config EXAMPLES_EASYLOGGER_STACKSIZE + int "Easylogger stack size" + default DEFAULT_TASK_STACKSIZE + +endif diff --git a/demo/os/nuttx-spiflash/apps/examples/easylogger/Make.defs b/demo/os/nuttx-spiflash/apps/examples/easylogger/Make.defs new file mode 100644 index 0000000..abb3ae6 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/examples/easylogger/Make.defs @@ -0,0 +1,23 @@ +############################################################################ +# apps/examples/easylogger/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_EXAMPLES_EASYLOGGER),) +CONFIGURED_APPS += $(APPDIR)/examples/easylogger +endif diff --git a/demo/os/nuttx-spiflash/apps/examples/easylogger/Makefile b/demo/os/nuttx-spiflash/apps/examples/easylogger/Makefile new file mode 100644 index 0000000..bb09c31 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/examples/easylogger/Makefile @@ -0,0 +1,38 @@ +############################################################################ +# apps/examples/easylogger/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +# Hello, easylogger! built-in application info + +PROGNAME = $(CONFIG_EXAMPLES_EASYLOGGER_PROGNAME) +PRIORITY = $(CONFIG_EXAMPLES_EASYLOGGER_PRIORITY) +STACKSIZE = $(CONFIG_EXAMPLES_EASYLOGGER_STACKSIZE) +MODULE = $(CONFIG_EXAMPLES_EASYLOGGER) + +# Hello, easylogger! Example + +MAINSRC = elog_main.c + +CFLAGS += ${shell $(INCDIR) "$(CC)" $(APPDIR)/system/easylogger/inc} +CFLAGS += ${shell $(INCDIR) "$(CC)" $(APPDIR)/system/easylogger/plugins/flash} +CFLAGS += ${shell $(INCDIR) "$(CC)" $(APPDIR)/system/easyflash/inc} + +include $(APPDIR)/Application.mk diff --git a/demo/os/nuttx-spiflash/apps/examples/easylogger/elog_main.c b/demo/os/nuttx-spiflash/apps/examples/easylogger/elog_main.c new file mode 100644 index 0000000..99d9ca2 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/examples/easylogger/elog_main.c @@ -0,0 +1,144 @@ +/* + * This file is part of the EasyLogger Library. + * + * Copyright (c) 2015-2017, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: nuttx demo. + * Created on: 2023-02-15 + */ + +#define LOG_TAG "elogdemo" + +#include +#include +#include +#include + +#include +#include + + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/** + * Env demo. + */ +static void test_env(void) { + uint32_t i_boot_times = NULL; + char *c_old_boot_times, c_new_boot_times[11] = {0}; + + /* get the boot count number from Env */ + c_old_boot_times = ef_get_env("boot_times"); +// assert_param(c_old_boot_times); + i_boot_times = atol(c_old_boot_times); + /* boot count +1 */ + i_boot_times ++; + printf("The system now boot %d times\n\r", i_boot_times); + /* interger to string */ + sprintf(c_new_boot_times,"%ld", i_boot_times); + /* set and store the boot count number to Env */ + ef_set_env("boot_times", c_new_boot_times); + ef_save_env(); +} + +/** + * EasyLogger demo + */ +static void test_elog(void) { + uint8_t buf[256]= {0}; + int i = 0; + + for (i = 0; i < sizeof(buf); i++) + { + buf[i] = i; + } + while(true) { + /* test log output for all level */ + log_a("Hello EasyLogger!"); + log_e("Hello EasyLogger!"); + log_w("Hello EasyLogger!"); + log_i("Hello EasyLogger!"); + log_d("Hello EasyLogger!"); + log_v("Hello EasyLogger!"); +// elog_raw("Hello EasyLogger!"); + elog_hexdump("test", 16, buf, sizeof(buf)); + break; + } +} + +static void easylogger_demo(void) { + /* close printf buffer */ + setbuf(stdout, NULL); + + /* initialize EasyFlash and EasyLogger */ + if ((easyflash_init() == EF_NO_ERR)&&(elog_init() == ELOG_NO_ERR)) { + /* set EasyLogger log format */ + elog_set_fmt(ELOG_LVL_ASSERT, ELOG_FMT_ALL); + elog_set_fmt(ELOG_LVL_ERROR, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME); + elog_set_fmt(ELOG_LVL_WARN, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME); + elog_set_fmt(ELOG_LVL_INFO, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME); + elog_set_fmt(ELOG_LVL_DEBUG, ELOG_FMT_ALL & ~ELOG_FMT_FUNC); + elog_set_fmt(ELOG_LVL_VERBOSE, ELOG_FMT_ALL & ~ELOG_FMT_FUNC); + #ifdef ELOG_COLOR_ENABLE + elog_set_text_color_enabled(true); + #endif + /* initialize EasyLogger Flash plugin */ + elog_flash_init(); + /* start EasyLogger */ + elog_start(); + + /* dynamic set enable or disable for output logs (true or false) */ + // elog_set_output_enabled(false); + /* dynamic set output logs's level (from ELOG_LVL_ASSERT to ELOG_LVL_VERBOSE) */ + // elog_set_filter_lvl(ELOG_LVL_WARN); + /* dynamic set output logs's filter for tag */ + // elog_set_filter_tag("main"); + /* dynamic set output logs's filter for keyword */ + // elog_set_filter_kw("Hello"); + /* dynamic set output logs's tag filter */ + // elog_set_filter_tag_lvl("main", ELOG_LVL_WARN); + + /* test logger output */ + test_env(); + test_elog(); + } + else{ + printf("easyflash_init or elog_init init fail\n"); + } + + return EXIT_SUCCESS; +} + + +/**************************************************************************** + * elog_demo + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + printf("Hello, EasyLogger!!\n"); + easylogger_demo(); + return 0; +} + diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/Kconfig b/demo/os/nuttx-spiflash/apps/system/easyflash/Kconfig new file mode 100644 index 0000000..780bab1 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/Kconfig @@ -0,0 +1,15 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +menuconfig SYSTEM_EASYFLASH + tristate "Easyflash" + default n + ---help--- + Enable support for the Easyflash + +if SYSTEM_EASYFLASH + + +endif # SYSTEM_EASYFLASH diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/Make.defs b/demo/os/nuttx-spiflash/apps/system/easyflash/Make.defs new file mode 100644 index 0000000..45806e3 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/Make.defs @@ -0,0 +1,23 @@ +############################################################################ +# apps/system/easyflash/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_SYSTEM_EASYFLASH),) +CONFIGURED_APPS += $(APPDIR)/system/easyflash +endif diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/Makefile b/demo/os/nuttx-spiflash/apps/system/easyflash/Makefile new file mode 100644 index 0000000..d50be04 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/Makefile @@ -0,0 +1,33 @@ +############################################################################ +# apps/system/easyflash/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +# easyflash Application + +CSRCS = easyflash.c ef_utils.c ef_port.c +CSRCS += ef_env.c # ef_env_wl.c +# CSRCS += ef_iap.c ef_log.c + +CFLAGS += ${shell $(INCDIR) "$(CC)" $(APPDIR)/system/easyflash/inc} + +VPATH += :src port + +include $(APPDIR)/Application.mk diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/inc/easyflash.h b/demo/os/nuttx-spiflash/apps/system/easyflash/inc/easyflash.h new file mode 100644 index 0000000..2aab0b1 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/inc/easyflash.h @@ -0,0 +1,107 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2014-2019, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: It is an head file for this library. You can see all be called functions. + * Created on: 2014-09-10 + */ + + +#ifndef EASYFLASH_H_ +#define EASYFLASH_H_ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* easyflash.c */ +EfErrCode easyflash_init(void); + +#ifdef EF_USING_ENV +/* only supported on ef_env.c */ +size_t ef_get_env_blob(const char *key, void *value_buf, size_t buf_len, size_t *saved_value_len); +bool ef_get_env_obj(const char *key, env_node_obj_t env); +size_t ef_read_env_value(env_node_obj_t env, uint8_t *value_buf, size_t buf_len); +EfErrCode ef_set_env_blob(const char *key, const void *value_buf, size_t buf_len); + +/* ef_env.c, ef_env_legacy_wl.c and ef_env_legacy.c */ +EfErrCode ef_load_env(void); +void ef_print_env(void); +char *ef_get_env(const char *key); +EfErrCode ef_set_env(const char *key, const char *value); +EfErrCode ef_del_env(const char *key); +EfErrCode ef_save_env(void); +EfErrCode ef_env_set_default(void); +size_t ef_get_env_write_bytes(void); +EfErrCode ef_set_and_save_env(const char *key, const char *value); +EfErrCode ef_del_and_save_env(const char *key); +#endif + +#ifdef EF_USING_IAP +/* ef_iap.c */ +EfErrCode ef_erase_bak_app(size_t app_size); +EfErrCode ef_erase_user_app(uint32_t user_app_addr, size_t user_app_size); +EfErrCode ef_erase_spec_user_app(uint32_t user_app_addr, size_t app_size, + EfErrCode (*app_erase)(uint32_t addr, size_t size)); +EfErrCode ef_erase_bl(uint32_t bl_addr, size_t bl_size); +EfErrCode ef_write_data_to_bak(uint8_t *data, size_t size, size_t *cur_size, + size_t total_size); +EfErrCode ef_copy_app_from_bak(uint32_t user_app_addr, size_t app_size); +EfErrCode ef_copy_spec_app_from_bak(uint32_t user_app_addr, size_t app_size, + EfErrCode (*app_write)(uint32_t addr, const uint32_t *buf, size_t size)); +EfErrCode ef_copy_bl_from_bak(uint32_t bl_addr, size_t bl_size); +uint32_t ef_get_bak_app_start_addr(void); +#endif + +#ifdef EF_USING_LOG +/* ef_log.c */ +EfErrCode ef_log_read(size_t index, uint32_t *log, size_t size); +EfErrCode ef_log_write(const uint32_t *log, size_t size); +EfErrCode ef_log_clean(void); +size_t ef_log_get_used_size(void); +#endif + +/* ef_utils.c */ +uint32_t ef_calc_crc32(uint32_t crc, const void *buf, size_t size); + +/* ef_port.c */ +EfErrCode ef_port_read(uint32_t addr, uint32_t *buf, size_t size); +EfErrCode ef_port_erase(uint32_t addr, size_t size); +EfErrCode ef_port_write(uint32_t addr, const uint32_t *buf, size_t size); +void ef_port_env_lock(void); +void ef_port_env_unlock(void); +void ef_log_debug(const char *file, const long line, const char *format, ...); +void ef_log_info(const char *format, ...); +void ef_print(const char *format, ...); + +#ifdef __cplusplus +} +#endif + +#endif /* EASYFLASH_H_ */ diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/inc/ef_cfg.h b/demo/os/nuttx-spiflash/apps/system/easyflash/inc/ef_cfg.h new file mode 100644 index 0000000..c257a50 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/inc/ef_cfg.h @@ -0,0 +1,79 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2015-2019, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: It is the configure head file for this library. + * Created on: 2015-07-14 + */ + +#ifndef EF_CFG_H_ +#define EF_CFG_H_ + +/* using ENV function, default is NG (Next Generation) mode start from V4.0 */ +#define EF_USING_ENV + +/* using IAP function */ +// #define EF_USING_IAP + +/* using save log function */ +// #define EF_USING_LOG + +/* the minimum size of flash erasure */ +#define EF_ERASE_MIN_SIZE 4096 + +/* the flash write granularity, unit: bit + * only support 1(nor flash)/ 8(stm32f4)/ 32(stm32f1)/ 64(stm32l4) */ +#define EF_WRITE_GRAN 1 + +/* + * + * This all Backup Area Flash storage index. All used flash area configure is under here. + * |----------------------------| Storage Size + * | Environment variables area | ENV area size @see ENV_AREA_SIZE + * |----------------------------| + * | Saved log area | Log area size @see LOG_AREA_SIZE + * |----------------------------| + * |(IAP)Downloaded application | IAP already downloaded application, unfixed size + * |----------------------------| + * + * @note all area sizes must be aligned with EF_ERASE_MIN_SIZE + * + * The EasyFlash add the NG (Next Generation) mode start from V4.0. All old mode before V4.0, called LEGACY mode. + * + * - NG (Next Generation) mode is default mode from V4.0. It's easy to settings, only defined the ENV_AREA_SIZE. + * - The LEGACY mode has been DEPRECATED. It is NOT RECOMMENDED to continue using. + * Beacuse it will use ram to buffer the ENV and spend more flash erase times. + * If you want use it please using the V3.X version. + */ + +/* backup area start address */ +#define EF_START_ADDR (0) /* from the SPI Flash position: 0KB*/ +/* ENV area size. It's at least one empty sector for GC. So it's definination must more then or equal 2 flash sector size. */ +#define ENV_AREA_SIZE (2 * EF_ERASE_MIN_SIZE) /* 8K */ +/* saved log area size */ +#define LOG_AREA_SIZE (10 * EF_ERASE_MIN_SIZE) /* 40K */ + +/* print debug information of flash */ +#define PRINT_DEBUG + +#endif /* EF_CFG_H_ */ diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/inc/ef_def.h b/demo/os/nuttx-spiflash/apps/system/easyflash/inc/ef_def.h new file mode 100644 index 0000000..0339c01 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/inc/ef_def.h @@ -0,0 +1,124 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2019-2020, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: It is the definitions head file for this library. + * Created on: 2019-11-20 + */ + +#ifndef EF_DEF_H_ +#define EF_DEF_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* EasyFlash software version number */ +#define EF_SW_VERSION "4.1.99" +#define EF_SW_VERSION_NUM 0x40199 + +/* + * ENV version number defined by user. + * Please change it when your firmware add a new ENV to default_env_set. + */ +#ifndef EF_ENV_VER_NUM +#define EF_ENV_VER_NUM 0 +#endif + +/* the ENV max name length must less then it */ +#ifndef EF_ENV_NAME_MAX +#define EF_ENV_NAME_MAX 32 +#endif + +/* EasyFlash debug print function. Must be implement by user. */ +#ifdef PRINT_DEBUG +#define EF_DEBUG(...) ef_log_debug(__FILE__, __LINE__, __VA_ARGS__) +#else +#define EF_DEBUG(...) +#endif +/* EasyFlash routine print function. Must be implement by user. */ +#define EF_INFO(...) ef_log_info(__VA_ARGS__) +/* EasyFlash assert for developer. */ +#define EF_ASSERT(EXPR) \ +if (!(EXPR)) \ +{ \ + EF_DEBUG("(%s) has assert failed at %s.\n", #EXPR, __FUNCTION__); \ + while (1); \ +} + +typedef struct _ef_env { + char *key; + void *value; + size_t value_len; +} ef_env, *ef_env_t; + +/* EasyFlash error code */ +typedef enum { + EF_NO_ERR, + EF_ERASE_ERR, + EF_READ_ERR, + EF_WRITE_ERR, + EF_ENV_NAME_ERR, + EF_ENV_NAME_EXIST, + EF_ENV_FULL, + EF_ENV_INIT_FAILED, +} EfErrCode; + +/* the flash sector current status */ +typedef enum { + EF_SECTOR_EMPTY, + EF_SECTOR_USING, + EF_SECTOR_FULL, +} EfSecrorStatus; + +enum env_status { + ENV_UNUSED, + ENV_PRE_WRITE, + ENV_WRITE, + ENV_PRE_DELETE, + ENV_DELETED, + ENV_ERR_HDR, + ENV_STATUS_NUM, +}; +typedef enum env_status env_status_t; + +struct env_node_obj { + env_status_t status; /**< ENV node status, @see node_status_t */ + bool crc_is_ok; /**< ENV node CRC32 check is OK */ + uint8_t name_len; /**< name length */ + uint32_t magic; /**< magic word(`K`, `V`, `4`, `0`) */ + uint32_t len; /**< ENV node total length (header + name + value), must align by EF_WRITE_GRAN */ + uint32_t value_len; /**< value length */ + char name[EF_ENV_NAME_MAX]; /**< name */ + struct { + uint32_t start; /**< ENV node start address */ + uint32_t value; /**< value start address */ + } addr; +}; +typedef struct env_node_obj *env_node_obj_t; + +#ifdef __cplusplus +} +#endif + +#endif /* EF_DEF_H_ */ diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/README.md b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/README.md new file mode 100644 index 0000000..9f70741 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/README.md @@ -0,0 +1,152 @@ +# EasyFlash Types plugin + +--- + +## 1 + +Ŀǰ EasyFlash Ὣַʽ洢 Flash Уģʽ£ڷַ͵ĻʹʱͱӶַת롣 Types Ϊ˷ûʹ EasyFlash ʱԸӼ򵥵ķʽȥ͵Ļ + +Ҫֵ֧ͰC **** **** Լ **ṹ** ڽṹͣ Types ڲ [struct2json](https://github.com/armink/struct2json) תĿҪ [struct2json](https://github.com/armink/struct2json) ⡣ + +## 2ʹ + +### 2.1 Դ뵼 + +֮ǰҪȷԼĿѰ EasyFlash Դ룬 "\easyflash\inc""\easyflash\port" "\easyflash\src" ļ뷽Բοֲĵ[](https://github.com/armink/EasyFlash/blob/master/docs/zh/port.md)ٽ Types Դ뵼뵽ĿУͬ "plugins\types" ļһ𿽱Ŀе easyflash ļ¡ȻҪ `easyflash\plugins\types\struct2json\inc` `easyflash\plugins\types` ļ·Ŀͷļ·мɡ + +### 2.2 ʼ + +```C +void ef_types_init(S2jHook *hook) +``` + +Ϊ Types ijʼҪʼ struct2json ڴĬʹõ malloc free ΪڴʹĬڴʽʼʹ RT-Thread ϵͳԴڴԲοijʼ룺 + +```C +S2jHook s2jHook = { + .free_fn = rt_free, + .malloc_fn = (void *(*)(size_t))rt_malloc, +}; +ef_types_init(&s2jHook); +``` + +### 2.3 + +#### 2.3.1 + +ڻ͵Ļ EasyFlash ԭе API һ£ֻ޸μεͣпõ API £ + +```C +bool ef_get_bool(const char *key); +char ef_get_char(const char *key); +short ef_get_short(const char *key); +int ef_get_int(const char *key); +long ef_get_long(const char *key); +float ef_get_float(const char *key); +double ef_get_double(const char *key); +EfErrCode ef_set_bool(const char *key, bool value); +EfErrCode ef_set_char(const char *key, char value); +EfErrCode ef_set_short(const char *key, short value); +EfErrCode ef_set_int(const char *key, int value); +EfErrCode ef_set_long(const char *key, long value); +EfErrCode ef_set_float(const char *key, float value); +EfErrCode ef_set_double(const char *key, double value); +``` + +#### 2.3.2 + +͵IJһ£ͬڣȡĻָͨ͵νзءпõ API £ + +```C +void ef_get_bool_array(const char *key, bool *value); +void ef_get_char_array(const char *key, char *value); +void ef_get_short_array(const char *key, short *value); +void ef_get_int_array(const char *key, int *value); +void ef_get_long_array(const char *key, long *value); +void ef_get_float_array(const char *key, float *value); +void ef_get_double_array(const char *key, double *value); +void ef_get_string_array(const char *key, char **value); +EfErrCode ef_set_bool_array(const char *key, bool *value, size_t len); +EfErrCode ef_set_char_array(const char *key, char *value, size_t len); +EfErrCode ef_set_short_array(const char *key, short *value, size_t len); +EfErrCode ef_set_int_array(const char *key, int *value, size_t len); +EfErrCode ef_set_long_array(const char *key, long *value, size_t len); +EfErrCode ef_set_float_array(const char *key, float *value, size_t len); +EfErrCode ef_set_double_array(const char *key, double *value, size_t len); +EfErrCode ef_set_string_array(const char *key, char **value, size_t len); +``` +#### 2.3.3 ṹ + +ڽṹͣҪʹ struct2json дýṹӦ JSON תٽдõĻתΪνʹáṹͻ API £ + +```C +void *ef_get_struct(const char *key, ef_types_get_cb get_cb); +EfErrCode ef_set_struct(const char *key, void *value, ef_types_set_cb set_cb); +``` + +ʹ̼ṹ JSON ֮ĻתԲο Demo + +```C +/* ṹ */ +typedef struct { + char name[16]; +} Hometown; +typedef struct { + uint8_t id; + double weight; + uint8_t score[8]; + char name[16]; + Hometown hometown; +} Student; + +/* ṹת JSON ķ */ +static cJSON *stu_set_cb(void* struct_obj) { + Student *struct_student = (Student *)struct_obj; + /* Student JSON */ + s2j_create_json_obj(json_student); + /* лݵ Student JSON */ + s2j_json_set_basic_element(json_student, struct_student, int, id); + s2j_json_set_basic_element(json_student, struct_student, double, weight); + s2j_json_set_array_element(json_student, struct_student, int, score, 8); + s2j_json_set_basic_element(json_student, struct_student, string, name); + /* лݵ Student.Hometown JSON */ + s2j_json_set_struct_element(json_hometown, json_student, struct_hometown, struct_student, Hometown, hometown); + s2j_json_set_basic_element(json_hometown, struct_hometown, string, name); + return json_student; +} + +/* JSON תṹķ */ +static void *stu_get_cb(cJSON* json_obj) { + /* Student ṹʾ s2j_ ͷķ struct2json ṩģ */ + s2j_create_struct_obj(struct_student, Student); + /* лݵ Student ṹ */ + s2j_struct_get_basic_element(struct_student, json_obj, int, id); + s2j_struct_get_array_element(struct_student, json_obj, int, score); + s2j_struct_get_basic_element(struct_student, json_obj, string, name); + s2j_struct_get_basic_element(struct_student, json_obj, double, weight); + /* лݵ Student.Hometown ṹ */ + s2j_struct_get_struct_element(struct_hometown, struct_student, json_hometown, json_obj, Hometown, hometown); + s2j_struct_get_basic_element(struct_hometown, json_hometown, string, name); + return struct_student; +} + +/* ýṹͻ */ +Student orignal_student = { + .id = 24, + .weight = 71.2, + .score = {1, 2, 3, 4, 5, 6, 7, 8}, + .name = "", + .hometown.name = "", +}; +ef_set_struct("ѧ", &orignal_student, stu_set_cb); + +/* ȡṹͻ */ +Student *student; +ef_get_struct("ѧ", student, stu_get_cb); + +/* ӡȡĽṹ */ +printf("%s ᣺%s \n", student->name, student->hometown.name); + +/* ͷŻȡṹͻпٵĶ̬ڴ */ +s2jHook.free_fn(student); +``` \ No newline at end of file diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/ef_types.c b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/ef_types.c new file mode 100644 index 0000000..8c516b9 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/ef_types.c @@ -0,0 +1,395 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2015-2016, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: Types plugin source code for this library. + * Created on: 2015-12-16 + */ + +#include "ef_types.h" +#include +#include + +/** + * array support types + */ +typedef enum { + EF_ARRAY_TYPES_BOOL, + EF_ARRAY_TYPES_CHAR, + EF_ARRAY_TYPES_SHORT, + EF_ARRAY_TYPES_INT, + EF_ARRAY_TYPES_LONG, + EF_ARRAY_TYPES_FLOAT, + EF_ARRAY_TYPES_DOUBLE, + EF_ARRAY_TYPES_STRING, +} ef_array_types; + +/** + * EasyFlash types plugin initialize. + * + * @param hook Memory management hook function. + * If hook is null or not call this function, then use free and malloc of C library. + */ +void ef_types_init(S2jHook *hook) { + s2j_init(hook); +} + +bool ef_get_bool(const char *key) { + char *value = ef_get_env(key); + if(value) { + return atoi(value) == 0 ? false : true; + } else { + EF_INFO("Couldn't find this ENV(%s)!\n", key); + return false; + } +} + +char ef_get_char(const char *key) { + return ef_get_long(key); +} + +short ef_get_short(const char *key) { + return ef_get_long(key); +} + +int ef_get_int(const char *key) { + return ef_get_long(key); +} + +long ef_get_long(const char *key) { + char *value = ef_get_env(key); + if(value) { + return atol(value); + } else { + EF_INFO("Couldn't find this ENV(%s)!\n", key); + return NULL; + } +} + +float ef_get_float(const char *key) { + return ef_get_double(key); +} + +double ef_get_double(const char *key) { + char *value = ef_get_env(key); + if(value) { + return atof(value); + } else { + EF_INFO("Couldn't find this ENV(%s)!\n", key); + return NULL; + } +} + +/** + * get array ENV value + * + * @param key ENV name + * @param value returned ENV value + * @param types ENV array's type + */ +static void ef_get_array(const char *key, void *value, ef_array_types types) { + char *char_value = ef_get_env(key); + cJSON *array; + size_t size, i; + + EF_ASSERT(value); + + if (char_value) { + array = cJSON_Parse(char_value); + if (array) { + size = cJSON_GetArraySize(array); + for (i = 0; i < size; i++) { + switch (types) { + case EF_ARRAY_TYPES_BOOL: { + *((bool *) value + i) = cJSON_GetArrayItem(array, i)->valueint; + break; + } + case EF_ARRAY_TYPES_CHAR: { + *((char *) value + i) = cJSON_GetArrayItem(array, i)->valueint; + break; + } + case EF_ARRAY_TYPES_SHORT: { + *((short *) value + i) = cJSON_GetArrayItem(array, i)->valueint; + break; + } + case EF_ARRAY_TYPES_INT: { + *((int *) value + i) = cJSON_GetArrayItem(array, i)->valueint; + break; + } + case EF_ARRAY_TYPES_LONG: { + *((long *) value + i) = cJSON_GetArrayItem(array, i)->valueint; + break; + } + case EF_ARRAY_TYPES_FLOAT: { + *((float *) value + i) = cJSON_GetArrayItem(array, i)->valuedouble; + break; + } + case EF_ARRAY_TYPES_DOUBLE: { + *((double *) value + i) = cJSON_GetArrayItem(array, i)->valuedouble; + break; + } + case EF_ARRAY_TYPES_STRING: { + *((char **) value + i) = cJSON_GetArrayItem(array, i)->valuestring; + break; + } + } + } + } else { + EF_INFO("This ENV(%s) value type has error!\n", key); + } + cJSON_Delete(array); + } else { + EF_INFO("Couldn't find this ENV(%s)!\n", key); + } +} + +void ef_get_bool_array(const char *key, bool *value) { + ef_get_array(key, value, EF_ARRAY_TYPES_BOOL); +} + +void ef_get_char_array(const char *key, char *value) { + ef_get_array(key, value, EF_ARRAY_TYPES_CHAR); +} + +void ef_get_short_array(const char *key, short *value) { + ef_get_array(key, value, EF_ARRAY_TYPES_SHORT); +} + +void ef_get_int_array(const char *key, int *value) { + ef_get_array(key, value, EF_ARRAY_TYPES_INT); +} + +void ef_get_long_array(const char *key, long *value) { + ef_get_array(key, value, EF_ARRAY_TYPES_LONG); +} + +void ef_get_float_array(const char *key, float *value) { + ef_get_array(key, value, EF_ARRAY_TYPES_FLOAT); +} + +void ef_get_double_array(const char *key, double *value) { + ef_get_array(key, value, EF_ARRAY_TYPES_DOUBLE); +} + +void ef_get_string_array(const char *key, char **value) { + ef_get_array(key, value, EF_ARRAY_TYPES_STRING); +} + +/** + * get structure ENV value + * + * @param key ENV name + * @param get_cb get structure callback function. + * You can use json to structure function which in the struct2json lib(https://github.com/armink/struct2json). + * + * @return value returned structure ENV value pointer. @note The returned value will malloc new ram. + * You must free the value then used finish. + */ +void *ef_get_struct(const char *key, ef_types_get_cb get_cb) { + char *char_value = ef_get_env(key); + cJSON *json_value = cJSON_Parse(char_value); + void *value = NULL; + + if (json_value) { + value = get_cb(json_value); + cJSON_Delete(json_value); + } + return value; +} + +EfErrCode ef_set_bool(const char *key, bool value) { + char char_value[2] = { 0 }; + if (!value) { + strcpy(char_value, "0"); + } else { + strcpy(char_value, "1"); + } + return ef_set_env(key, char_value); +} + +EfErrCode ef_set_char(const char *key, char value) { + return ef_set_long(key, value); +} + +EfErrCode ef_set_short(const char *key, short value) { + return ef_set_long(key, value); +} + +EfErrCode ef_set_int(const char *key, int value) { + return ef_set_long(key, value); +} + +EfErrCode ef_set_long(const char *key, long value) { + char char_value[21] = { 0 }; + + snprintf(char_value, 20, "%ld", value); + + return ef_set_env(key, char_value); +} + +EfErrCode ef_set_float(const char *key, float value) { + return ef_set_double(key, value); +} + +EfErrCode ef_set_double(const char *key, double value) { + char char_value[21] = { 0 }; + + snprintf(char_value, 20, "%lf", value); + + return ef_set_env(key, char_value); +} + +/** + * set array ENV value + * + * @param key ENV name + * @param value ENV value + * @param len array length + * @param types ENV array's type + * + * @return ENV set result + */ +static EfErrCode ef_set_array(const char *key, void *value, size_t len, ef_array_types types) { + char *char_value = NULL; + cJSON *array = NULL, *array_item = NULL; + size_t i; + EfErrCode result = EF_NO_ERR; + + EF_ASSERT(value); + + array = cJSON_CreateArray(); + if (array) { + for (i = 0; i < len; i++) { + switch (types) { + case EF_ARRAY_TYPES_BOOL: { + array_item = cJSON_CreateBool(*((bool *) value + i)); + break; + } + case EF_ARRAY_TYPES_CHAR: { + array_item = cJSON_CreateNumber(*((char *) value + i)); + break; + } + case EF_ARRAY_TYPES_SHORT: { + array_item = cJSON_CreateNumber(*((short *) value + i)); + break; + } + case EF_ARRAY_TYPES_INT: { + array_item = cJSON_CreateNumber(*((int *) value + i)); + break; + } + case EF_ARRAY_TYPES_LONG: { + array_item = cJSON_CreateNumber(*((long *) value + i)); + break; + } + case EF_ARRAY_TYPES_FLOAT: { + array_item = cJSON_CreateNumber(*((float *) value + i)); + break; + } + case EF_ARRAY_TYPES_DOUBLE: { + array_item = cJSON_CreateNumber(*((double *) value + i)); + break; + } + case EF_ARRAY_TYPES_STRING: { + array_item = cJSON_CreateString(*((char **) value + i)); + break; + } + default: + /* the types parameter has error */ + EF_ASSERT(0); + } + if (array_item) { + cJSON_AddItemToArray(array, array_item); + } else { + result = EF_ENV_FULL; + EF_INFO("Memory full!\n", key); + break; + } + } + char_value = cJSON_PrintUnformatted(array); + if (char_value) { + result = ef_set_env(key, char_value); + s2jHook.free_fn(char_value); + } else { + result = EF_ENV_FULL; + EF_INFO("Memory full!\n", key); + } + cJSON_Delete(array); + } else { + result = EF_ENV_FULL; + EF_INFO("Memory full!\n", key); + } + return result; +} + +EfErrCode ef_set_bool_array(const char *key, bool *value, size_t len) { + return ef_set_array(key, value, len, EF_ARRAY_TYPES_BOOL); +} + +EfErrCode ef_set_char_array(const char *key, char *value, size_t len) { + return ef_set_array(key, value, len, EF_ARRAY_TYPES_CHAR); +} + +EfErrCode ef_set_short_array(const char *key, short *value, size_t len) { + return ef_set_array(key, value, len, EF_ARRAY_TYPES_SHORT); +} + +EfErrCode ef_set_int_array(const char *key, int *value, size_t len) { + return ef_set_array(key, value, len, EF_ARRAY_TYPES_INT); +} + +EfErrCode ef_set_long_array(const char *key, long *value, size_t len) { + return ef_set_array(key, value, len, EF_ARRAY_TYPES_LONG); +} + +EfErrCode ef_set_float_array(const char *key, float *value, size_t len) { + return ef_set_array(key, value, len, EF_ARRAY_TYPES_FLOAT); +} + +EfErrCode ef_set_double_array(const char *key, double *value, size_t len) { + return ef_set_array(key, value, len, EF_ARRAY_TYPES_DOUBLE); +} + +EfErrCode ef_set_string_array(const char *key, char **value, size_t len) { + return ef_set_array(key, value, len, EF_ARRAY_TYPES_STRING); +} + +/** + * set structure ENV value + * + * @param key ENV name + * @param value structure ENV value pointer + * @param get_cb set structure callback function. + * You can use structure to json function which in the struct2json lib(https://github.com/armink/struct2json). + */ +EfErrCode ef_set_struct(const char *key, void *value, ef_types_set_cb set_cb) { + EfErrCode result = EF_NO_ERR; + cJSON *json_value = set_cb(value); + char *char_value = cJSON_PrintUnformatted(json_value); + + result = ef_set_env(key, char_value); + + cJSON_Delete(json_value); + s2jHook.free_fn(char_value); + + return result; +} diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/ef_types.h b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/ef_types.h new file mode 100644 index 0000000..f861898 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/ef_types.h @@ -0,0 +1,76 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2015-2016, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: It is an head file for this plugin. You can see all be called functions. + * Created on: 2015-12-16 + */ + +#ifndef EF_TYPES_H_ +#define EF_TYPES_H_ + +#include +#include +#include "struct2json\inc\s2j.h" + +/* EasyFlash types plugin's software version number */ +#define EF_TYPES_SW_VERSION "0.11.03" + +typedef cJSON *(*ef_types_set_cb)(void* struct_obj); +typedef void *(*ef_types_get_cb)(cJSON* json_obj); + +void ef_types_init(S2jHook *hook); +bool ef_get_bool(const char *key); +char ef_get_char(const char *key); +short ef_get_short(const char *key); +int ef_get_int(const char *key); +long ef_get_long(const char *key); +float ef_get_float(const char *key); +double ef_get_double(const char *key); +void ef_get_bool_array(const char *key, bool *value); +void ef_get_char_array(const char *key, char *value); +void ef_get_short_array(const char *key, short *value); +void ef_get_int_array(const char *key, int *value); +void ef_get_long_array(const char *key, long *value); +void ef_get_float_array(const char *key, float *value); +void ef_get_double_array(const char *key, double *value); +void ef_get_string_array(const char *key, char **value); +void *ef_get_struct(const char *key, ef_types_get_cb get_cb); +EfErrCode ef_set_bool(const char *key, bool value); +EfErrCode ef_set_char(const char *key, char value); +EfErrCode ef_set_short(const char *key, short value); +EfErrCode ef_set_int(const char *key, int value); +EfErrCode ef_set_long(const char *key, long value); +EfErrCode ef_set_float(const char *key, float value); +EfErrCode ef_set_double(const char *key, double value); +EfErrCode ef_set_bool_array(const char *key, bool *value, size_t len); +EfErrCode ef_set_char_array(const char *key, char *value, size_t len); +EfErrCode ef_set_short_array(const char *key, short *value, size_t len); +EfErrCode ef_set_int_array(const char *key, int *value, size_t len); +EfErrCode ef_set_long_array(const char *key, long *value, size_t len); +EfErrCode ef_set_float_array(const char *key, float *value, size_t len); +EfErrCode ef_set_double_array(const char *key, double *value, size_t len); +EfErrCode ef_set_string_array(const char *key, char **value, size_t len); +EfErrCode ef_set_struct(const char *key, void *value, ef_types_set_cb set_cb); + +#endif /* EF_TYPES_H_ */ diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/inc/cJSON.h b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/inc/cJSON.h new file mode 100644 index 0000000..634fe22 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/inc/cJSON.h @@ -0,0 +1,154 @@ +/* + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* cJSON Types: */ +#define cJSON_False 0 +#define cJSON_True 1 +#define cJSON_NULL 2 +#define cJSON_Number 3 +#define cJSON_String 4 +#define cJSON_Array 5 +#define cJSON_Object 6 + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON { + struct cJSON *next,*prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + + int type; /* The type of the item, as above. */ + + char *valuestring; /* The item's string, if type==cJSON_String */ + int valueint; /* The item's number, if type==cJSON_Number */ + double valuedouble; /* The item's number, if type==cJSON_Number */ + + char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ +} cJSON; + +typedef struct cJSON_Hooks { + void *(*malloc_fn)(size_t sz); + void (*free_fn)(void *ptr); +} cJSON_Hooks; + +/* Supply malloc, realloc and free functions to cJSON */ +extern void cJSON_InitHooks(cJSON_Hooks* hooks); + + +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */ +extern cJSON *cJSON_Parse(const char *value); +/* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */ +extern char *cJSON_Print(cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */ +extern char *cJSON_PrintUnformatted(cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +extern char *cJSON_PrintBuffered(cJSON *item,int prebuffer,int fmt); +/* Delete a cJSON entity and all subentities. */ +extern void cJSON_Delete(cJSON *c); + +/* Returns the number of items in an array (or object). */ +extern int cJSON_GetArraySize(cJSON *array); +/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ +extern cJSON *cJSON_GetArrayItem(cJSON *array,int item); +/* Get item "string" from object. Case insensitive. */ +extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string); +extern int cJSON_HasObjectItem(cJSON *object,const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +extern const char *cJSON_GetErrorPtr(void); + +/* These calls create a cJSON item of the appropriate type. */ +extern cJSON *cJSON_CreateNull(void); +extern cJSON *cJSON_CreateTrue(void); +extern cJSON *cJSON_CreateFalse(void); +extern cJSON *cJSON_CreateBool(int b); +extern cJSON *cJSON_CreateNumber(double num); +extern cJSON *cJSON_CreateString(const char *string); +extern cJSON *cJSON_CreateArray(void); +extern cJSON *cJSON_CreateObject(void); + +/* These utilities create an Array of count items. */ +extern cJSON *cJSON_CreateIntArray(const int *numbers,int count); +extern cJSON *cJSON_CreateFloatArray(const float *numbers,int count); +extern cJSON *cJSON_CreateDoubleArray(const double *numbers,int count); +extern cJSON *cJSON_CreateStringArray(const char **strings,int count); + +/* Append item to the specified array/object. */ +extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); +extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item); +extern void cJSON_AddItemToObjectCS(cJSON *object,const char *string,cJSON *item); /* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object */ +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +extern void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item); + +/* Remove/Detatch items from Arrays/Objects. */ +extern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which); +extern void cJSON_DeleteItemFromArray(cJSON *array,int which); +extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string); +extern void cJSON_DeleteItemFromObject(cJSON *object,const char *string); + +/* Update array items. */ +extern void cJSON_InsertItemInArray(cJSON *array,int which,cJSON *newitem); /* Shifts pre-existing items to the right. */ +extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem); +extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +extern cJSON *cJSON_Duplicate(cJSON *item,int recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will +need to be released. With recurse!=0, it will duplicate any children connected to the item. +The item->next and ->prev pointers are always zero on return from Duplicate. */ + +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +extern cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated); + +extern void cJSON_Minify(char *json); + +/* Macros for creating things quickly. */ +#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) +#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) +#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) +#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) +#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) +#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) +#define cJSON_SetNumberValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) + +/* Macro for iterating over an array */ +#define cJSON_ArrayForEach(pos, head) for(pos = (head)->child; pos != NULL; pos = pos->next) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/inc/s2j.h b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/inc/s2j.h new file mode 100644 index 0000000..73ec4df --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/inc/s2j.h @@ -0,0 +1,91 @@ +/* + * This file is part of the struct2json Library. + * + * Copyright (c) 2015, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: It is an head file for this library. You can see all be called functions. + * Created on: 2015-10-14 + */ + +#ifndef __S2J_H__ +#define __S2J_H__ + +#include +#include +#include "s2jdef.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* struct2json software version number */ +#define S2J_SW_VERSION "1.0.2" + +/* Create JSON object */ +#define s2j_create_json_obj(json_obj) \ + S2J_CREATE_JSON_OBJECT(json_obj) + +/* Delete JSON object */ +#define s2j_delete_json_obj(json_obj) \ + S2J_DELETE_JSON_OBJECT(json_obj) + +/* Set basic type element for JSON object */ +#define s2j_json_set_basic_element(to_json, from_struct, type, element) \ + S2J_JSON_SET_BASIC_ELEMENT(to_json, from_struct, type, element) + +/* Set array type element for JSON object */ +#define s2j_json_set_array_element(to_json, from_struct, type, element, size) \ + S2J_JSON_SET_ARRAY_ELEMENT(to_json, from_struct, type, element, size) + +/* Set child structure type element for JSON object */ +#define s2j_json_set_struct_element(child_json, to_json, child_struct, from_struct, type, element) \ + S2J_JSON_SET_STRUCT_ELEMENT(child_json, to_json, child_struct, from_struct, type, element) + +/* Create structure object */ +#define s2j_create_struct_obj(struct_obj, type) \ + S2J_CREATE_STRUCT_OBJECT(struct_obj, type) + +/* Delete structure object */ +#define s2j_delete_struct_obj(struct_obj) \ + S2J_DELETE_STRUCT_OBJECT(struct_obj) + +/* Get basic type element for structure object */ +#define s2j_struct_get_basic_element(to_struct, from_json, type, element) \ + S2J_STRUCT_GET_BASIC_ELEMENT(to_struct, from_json, type, element) + +/* Get array type element for structure object */ +#define s2j_struct_get_array_element(to_struct, from_json, type, element) \ + S2J_STRUCT_GET_ARRAY_ELEMENT(to_struct, from_json, type, element) + +/* Get child structure type element for structure object */ +#define s2j_struct_get_struct_element(child_struct, to_struct, child_json, from_json, type, element) \ + S2J_STRUCT_GET_STRUCT_ELEMENT(child_struct, to_struct, child_json, from_json, type, element) + +/* s2j.c */ +extern S2jHook s2jHook; +void s2j_init(S2jHook *hook); + +#ifdef __cplusplus +} +#endif + +#endif /* __S2J_H__ */ diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/inc/s2jdef.h b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/inc/s2jdef.h new file mode 100644 index 0000000..be445ac --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/inc/s2jdef.h @@ -0,0 +1,150 @@ +/* + * This file is part of the struct2json Library. + * + * Copyright (c) 2015, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: It is an head file for this library. + * Created on: 2015-10-14 + */ + +#ifndef __S2JDEF_H__ +#define __S2JDEF_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + void *(*malloc_fn)(size_t sz); + void (*free_fn)(void *ptr); +} S2jHook, *S2jHook_t; + +#define S2J_STRUCT_GET_int_ELEMENT(to_struct, from_json, _element) \ + json_temp = cJSON_GetObjectItem(from_json, #_element); \ + if (json_temp) (to_struct)->_element = json_temp->valueint; + +#define S2J_STRUCT_GET_string_ELEMENT(to_struct, from_json, _element) \ + json_temp = cJSON_GetObjectItem(from_json, #_element); \ + if (json_temp) strcpy((to_struct)->_element, json_temp->valuestring); + +#define S2J_STRUCT_GET_double_ELEMENT(to_struct, from_json, _element) \ + json_temp = cJSON_GetObjectItem(from_json, #_element); \ + if (json_temp) (to_struct)->_element = json_temp->valuedouble; + +#define S2J_STRUCT_ARRAY_GET_int_ELEMENT(to_struct, from_json, _element, index) \ + (to_struct)->_element[index] = from_json->valueint; + +#define S2J_STRUCT_ARRAY_GET_string_ELEMENT(to_struct, from_json, _element, index) \ + strcpy((to_struct)->_element[index], from_json->valuestring); + +#define S2J_STRUCT_ARRAY_GET_double_ELEMENT(to_struct, from_json, _element, index) \ + (to_struct)->_element[index] = from_json->valuedouble; + +#define S2J_STRUCT_ARRAY_GET_ELEMENT(to_struct, from_json, type, _element, index) \ + S2J_STRUCT_ARRAY_GET_##type##_ELEMENT(to_struct, from_json, _element, index) + +#define S2J_JSON_SET_int_ELEMENT(to_json, from_struct, _element) \ + cJSON_AddNumberToObject(to_json, #_element, (from_struct)->_element); + +#define S2J_JSON_SET_double_ELEMENT(to_json, from_struct, _element) \ + cJSON_AddNumberToObject(to_json, #_element, (from_struct)->_element); + +#define S2J_JSON_SET_string_ELEMENT(to_json, from_struct, _element) \ + cJSON_AddStringToObject(to_json, #_element, (from_struct)->_element); + +#define S2J_JSON_ARRAY_SET_int_ELEMENT(to_json, from_struct, _element, index) \ + cJSON_AddItemToArray(to_json, cJSON_CreateNumber((from_struct)->_element[index])); + +#define S2J_JSON_ARRAY_SET_double_ELEMENT(to_json, from_struct, _element, index) \ + cJSON_AddItemToArray(to_json, cJSON_CreateNumber((from_struct)->_element[index])); + +#define S2J_JSON_ARRAY_SET_string_ELEMENT(to_json, from_struct, _element, index) \ + cJSON_AddItemToArray(to_json, cJSON_CreateString((from_struct)->_element[index])); + +#define S2J_JSON_ARRAY_SET_ELEMENT(to_json, from_struct, type, _element, index) \ + S2J_JSON_ARRAY_SET_##type##_ELEMENT(to_json, from_struct, _element, index) + + +#define S2J_CREATE_JSON_OBJECT(json_obj) \ + cJSON *json_obj = cJSON_CreateObject(); + +#define S2J_DELETE_JSON_OBJECT(json_obj) \ + cJSON_Delete(json_obj); + +#define S2J_JSON_SET_BASIC_ELEMENT(to_json, from_struct, type, _element) \ + S2J_JSON_SET_##type##_ELEMENT(to_json, from_struct, _element) + +#define S2J_JSON_SET_ARRAY_ELEMENT(to_json, from_struct, type, _element, size) \ + { \ + cJSON *array; \ + size_t index = 0; \ + array = cJSON_CreateArray(); \ + if (array) { \ + while (index < size) { \ + S2J_JSON_ARRAY_SET_ELEMENT(array, from_struct, type, _element, index++); \ + } \ + cJSON_AddItemToObject(to_json, #_element, array); \ + } \ + } + +#define S2J_JSON_SET_STRUCT_ELEMENT(child_json, to_json, child_struct, from_struct, type, _element) \ + type *child_struct = &((from_struct)->_element); \ + cJSON *child_json = cJSON_CreateObject(); \ + if (child_json) cJSON_AddItemToObject(to_json, #_element, child_json); + +#define S2J_CREATE_STRUCT_OBJECT(struct_obj, type) \ + cJSON *json_temp; \ + type *struct_obj = s2jHook.malloc_fn(sizeof(type)); \ + if (struct_obj) memset(struct_obj, 0, sizeof(type)); + +#define S2J_DELETE_STRUCT_OBJECT(struct_obj) \ + s2jHook.free_fn(struct_obj); + +#define S2J_STRUCT_GET_BASIC_ELEMENT(to_struct, from_json, type, _element) \ + S2J_STRUCT_GET_##type##_ELEMENT(to_struct, from_json, _element) + +#define S2J_STRUCT_GET_ARRAY_ELEMENT(to_struct, from_json, type, _element) \ + { \ + cJSON *array, *array_element; \ + size_t index = 0, size = 0; \ + array = cJSON_GetObjectItem(from_json, #_element); \ + if (array) { \ + size = cJSON_GetArraySize(array); \ + while (index < size) { \ + array_element = cJSON_GetArrayItem(array, index); \ + if (array_element) S2J_STRUCT_ARRAY_GET_ELEMENT(to_struct, array_element, type, _element, index++); \ + } \ + } \ + } + +#define S2J_STRUCT_GET_STRUCT_ELEMENT(child_struct, to_struct, child_json, from_json, type, _element) \ + type *child_struct = &((to_struct)->_element); \ + cJSON *child_json = cJSON_GetObjectItem(from_json, #_element); + +#ifdef __cplusplus +} +#endif + +#endif /* __S2JDEF_H__ */ diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/readme.md b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/readme.md new file mode 100644 index 0000000..dea28be --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/readme.md @@ -0,0 +1,61 @@ +# C结构体与 JSON 快速互转库 + +--- + +## struct2json + +[struct2json](https://github.com/armink/struct2json) 是一个开源的C结构体与 JSON 快速互转库,它可以快速实现 **结构体对象** 与 **JSON 对象** 之间序列化及反序列化要求。快速、简洁的 API 设计,大大降低直接使用 JSON 解析库来实现此类功能的代码复杂度。 + +## 起源 + +把面向对象设计应用到C语言中,是当下很流行的设计思想。由于C语言中没有类,所以一般使用结构体 `struct` 充当类,那么结构体变量就是对象。有了对象之后,很多时候需要考虑对象的序列化及反序列化问题。C语言不像很多高级语言拥有反射等机制,使得对象序列化及反序列化被原生的支持。 + +对于C语言来说,序列化为 JSON 字符串是个不错的选择,所以就得使用 [cJSON](https://github.com/kbranigan/cJSON) 这类 JSON 解析库,但是使用后的代码冗余且逻辑性差,所以萌生对cJSON库进行二次封装,实现一个 struct 与 JSON 之间快速互转的库。 struct2json 就诞生于此。下面是 struct2json 主要使用场景: + +- **持久化** :结构体对象序列化为 JSON 对象后,可直接保存至文件、Flash,实现对结构体对象的掉电存储; +- **通信** :高级语言对JSON支持的很友好,例如: Javascript、Groovy 就对 JSON 具有原生的支持,所以 JSON 也可作为C语言与其他语言软件之间的通信协议格式及对象传递格式; +- **可视化** :序列化为 JSON 后的对象,可以更加直观的展示到控制台或者 UI 上,可用于产品调试、产品二次开发等场景; + +## 如何使用 + +### 声明结构体 + +如下声明了两个结构体,结构体 `Hometown` 是结构体 `Student` 的子结构体 + +```C +/* 籍贯 */ +typedef struct { + char name[16]; +} Hometown; + +/* 学生 */ +typedef struct { + uint8_t id; + uint8_t score[8]; + char name[10]; + double weight; + Hometown hometown; +} Student; +``` + +### 将结构体对象序列化为 JSON 对象 + +|使用前([源文件](https://github.com/armink/struct2json/blob/master/docs/zh/assets/not_use_struct2json.c))|使用后([源文件](https://github.com/armink/struct2json/blob/master/docs/zh/assets/used_struct2json.c))| +|:-----:|:-----:| +|![结构体转JSON-使用前](https://git.oschina.net/Armink/struct2json/raw/master/docs/zh/images/not_use_struct2json.png)| ![结构体转JSON-使用后](https://git.oschina.net/Armink/struct2json/raw/master/docs/zh/images/used_struct2json.png)| + +### 将 JSON 对象反序列化为结构体对象 + +|使用前([源文件](https://github.com/armink/struct2json/blob/master/docs/zh/assets/not_use_struct2json_for_json.c))|使用后([源文件](https://github.com/armink/struct2json/blob/master/docs/zh/assets/used_struct2json_for_json.c))| +|:-----:|:-----:| +|![JSON转结构体-使用前](https://git.oschina.net/Armink/struct2json/raw/master/docs/zh/images/not_use_struct2json_for_json.png)| ![JSON转结构体-使用后](https://git.oschina.net/Armink/struct2json/raw/master/docs/zh/images/used_struct2json_for_json.png)| + +欢迎大家 **fork and pull request**([Github](https://github.com/armink/struct2json)|[OSChina](http://git.oschina.net/armink/struct2json)|[Coding](https://coding.net/u/armink/p/struct2json/git)) 。如果觉得这个开源项目很赞,可以点击[项目主页](https://github.com/armink/struct2json) 右上角的**Star**,同时把它推荐给更多有需要的朋友。 + +## 文档 + +具体内容参考[`\docs\zh\`](https://github.com/armink/struct2json/tree/master/docs/zh)下的文件。务必保证在 **阅读文档** 后再使用。 + +## 许可 + +MIT Copyright (c) armink.ztl@gmail.com diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/src/cJSON.c b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/src/cJSON.c new file mode 100644 index 0000000..67afd8f --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/src/cJSON.c @@ -0,0 +1,762 @@ +/* + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +#include +#include +#include +#include +#include +#include +#include +#include "cJSON.h" + +static const char *ep; + +const char *cJSON_GetErrorPtr(void) {return ep;} + +static int cJSON_strcasecmp(const char *s1,const char *s2) +{ + if (!s1) return (s1==s2)?0:1;if (!s2) return 1; + for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) if(*s1 == 0) return 0; + return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2); +} + +static void *(*cJSON_malloc)(size_t sz) = malloc; +static void (*cJSON_free)(void *ptr) = free; + +static char* cJSON_strdup(const char* str) +{ + size_t len; + char* copy; + + len = strlen(str) + 1; + if ((copy = (char*)cJSON_malloc(len)) == NULL) return 0; + memcpy(copy,str,len); + return copy; +} + +void cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (!hooks) { /* Reset hooks */ + cJSON_malloc = malloc; + cJSON_free = free; + return; + } + + cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc; + cJSON_free = (hooks->free_fn)?hooks->free_fn:free; +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(void) +{ + cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON)); + if (node) memset(node,0,sizeof(cJSON)); + return node; +} + +/* Delete a cJSON structure. */ +void cJSON_Delete(cJSON *c) +{ + cJSON *next; + while (c) + { + next=c->next; + if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child); + if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring); + if (!(c->type&cJSON_StringIsConst) && c->string) cJSON_free(c->string); + cJSON_free(c); + c=next; + } +} + +/* Parse the input text to generate a number, and populate the result into item. */ +static const char *parse_number(cJSON *item,const char *num) +{ + double n=0,sign=1,scale=0;int subscale=0,signsubscale=1; + + if (*num=='-') sign=-1,num++; /* Has sign? */ + if (*num=='0') num++; /* is zero */ + if (*num>='1' && *num<='9') do n=(n*10.0)+(*num++ -'0'); while (*num>='0' && *num<='9'); /* Number? */ + if (*num=='.' && num[1]>='0' && num[1]<='9') {num++; do n=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');} /* Fractional part? */ + if (*num=='e' || *num=='E') /* Exponent? */ + { num++;if (*num=='+') num++; else if (*num=='-') signsubscale=-1,num++; /* With sign? */ + while (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0'); /* Number? */ + } + + n=sign*n*pow(10.0,(scale+subscale*signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */ + + item->valuedouble=n; + item->valueint=(int)n; + item->type=cJSON_Number; + return num; +} + +static int pow2gt (int x) { --x; x|=x>>1; x|=x>>2; x|=x>>4; x|=x>>8; x|=x>>16; return x+1; } + +typedef struct {char *buffer; int length; int offset; } printbuffer; + +static char* ensure(printbuffer *p,int needed) +{ + char *newbuffer;int newsize; + if (!p || !p->buffer) return 0; + needed+=p->offset; + if (needed<=p->length) return p->buffer+p->offset; + + newsize=pow2gt(needed); + newbuffer=(char*)cJSON_malloc(newsize); + if (!newbuffer) {cJSON_free(p->buffer);p->length=0,p->buffer=0;return 0;} + if (newbuffer) memcpy(newbuffer,p->buffer,p->length); + cJSON_free(p->buffer); + p->length=newsize; + p->buffer=newbuffer; + return newbuffer+p->offset; +} + +static int update(printbuffer *p) +{ + char *str; + if (!p || !p->buffer) return 0; + str=p->buffer+p->offset; + return p->offset+strlen(str); +} + +/* Render the number nicely from the given item into a string. */ +static char *print_number(cJSON *item,printbuffer *p) +{ + char *str=0; + double d=item->valuedouble; + if (d==0) + { + if (p) str=ensure(p,2); + else str=(char*)cJSON_malloc(2); /* special case for 0. */ + if (str) strcpy(str,"0"); + } + else if (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN) + { + if (p) str=ensure(p,21); + else str=(char*)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */ + if (str) sprintf(str,"%d",item->valueint); + } + else + { + if (p) str=ensure(p,64); + else str=(char*)cJSON_malloc(64); /* This is a nice tradeoff. */ + if (str) + { + if (fpclassify(d) != FP_ZERO && !isnormal(d)) sprintf(str,"null"); + else if (fabs(floor(d)-d)<=DBL_EPSILON && fabs(d)<1.0e60) sprintf(str,"%.0f",d); + else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str,"%e",d); + else sprintf(str,"%f",d); + } + } + return str; +} + +static unsigned parse_hex4(const char *str) +{ + unsigned h=0; + if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0; + h=h<<4;str++; + if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0; + h=h<<4;str++; + if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0; + h=h<<4;str++; + if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0; + return h; +} + +/* Parse the input text into an unescaped cstring, and populate item. */ +static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; +static const char *parse_string(cJSON *item,const char *str) +{ + const char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc,uc2; + if (*str!='\"') {ep=str;return 0;} /* not a string! */ + + while (*ptr!='\"' && *ptr && ++len) if (*ptr++ == '\\') ptr++; /* Skip escaped quotes. */ + + out=(char*)cJSON_malloc(len+1); /* This is how long we need for the string, roughly. */ + if (!out) return 0; + + ptr=str+1;ptr2=out; + while (*ptr!='\"' && *ptr) + { + if (*ptr!='\\') *ptr2++=*ptr++; + else + { + ptr++; + switch (*ptr) + { + case 'b': *ptr2++='\b'; break; + case 'f': *ptr2++='\f'; break; + case 'n': *ptr2++='\n'; break; + case 'r': *ptr2++='\r'; break; + case 't': *ptr2++='\t'; break; + case 'u': /* transcode utf16 to utf8. */ + uc=parse_hex4(ptr+1);ptr+=4; /* get the unicode char. */ + + if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0) break; /* check for invalid. */ + + if (uc>=0xD800 && uc<=0xDBFF) /* UTF16 surrogate pairs. */ + { + if (ptr[1]!='\\' || ptr[2]!='u') break; /* missing second-half of surrogate. */ + uc2=parse_hex4(ptr+3);ptr+=6; + if (uc2<0xDC00 || uc2>0xDFFF) break; /* invalid second-half of surrogate. */ + uc=0x10000 + (((uc&0x3FF)<<10) | (uc2&0x3FF)); + } + + len=4;if (uc<0x80) len=1;else if (uc<0x800) len=2;else if (uc<0x10000) len=3; ptr2+=len; + + switch (len) { + case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 1: *--ptr2 =(uc | firstByteMark[len]); + } + ptr2+=len; + break; + default: *ptr2++=*ptr; break; + } + ptr++; + } + } + *ptr2=0; + if (*ptr=='\"') ptr++; + item->valuestring=out; + item->type=cJSON_String; + return ptr; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static char *print_string_ptr(const char *str,printbuffer *p) +{ + const char *ptr;char *ptr2,*out;int len=0,flag=0;unsigned char token; + + for (ptr=str;*ptr;ptr++) flag|=((*ptr>0 && *ptr<32)||(*ptr=='\"')||(*ptr=='\\'))?1:0; + if (!flag) + { + len=ptr-str; + if (p) out=ensure(p,len+3); + else out=(char*)cJSON_malloc(len+3); + if (!out) return 0; + ptr2=out;*ptr2++='\"'; + strcpy(ptr2,str); + ptr2[len]='\"'; + ptr2[len+1]=0; + return out; + } + + if (!str) + { + if (p) out=ensure(p,3); + else out=(char*)cJSON_malloc(3); + if (!out) return 0; + strcpy(out,"\"\""); + return out; + } + ptr=str;while (('\0' != (token=*ptr)) && ++len) {if (NULL != strchr("\"\\\b\f\n\r\t",token)) len++; else if (token<32) len+=5;ptr++;} + + if (p) out=ensure(p,len+3); + else out=(char*)cJSON_malloc(len+3); + if (!out) return 0; + + ptr2=out;ptr=str; + *ptr2++='\"'; + while (*ptr) + { + if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++; + else + { + *ptr2++='\\'; + switch (token=*ptr++) + { + case '\\': *ptr2++='\\'; break; + case '\"': *ptr2++='\"'; break; + case '\b': *ptr2++='b'; break; + case '\f': *ptr2++='f'; break; + case '\n': *ptr2++='n'; break; + case '\r': *ptr2++='r'; break; + case '\t': *ptr2++='t'; break; + default: sprintf(ptr2,"u%04x",token);ptr2+=5; break; /* escape and print */ + } + } + } + *ptr2++='\"';*ptr2++=0; + return out; +} +/* Invote print_string_ptr (which is useful) on an item. */ +static char *print_string(cJSON *item,printbuffer *p) {return print_string_ptr(item->valuestring,p);} + +/* Predeclare these prototypes. */ +static const char *parse_value(cJSON *item,const char *value); +static char *print_value(cJSON *item,int depth,int fmt,printbuffer *p); +static const char *parse_array(cJSON *item,const char *value); +static char *print_array(cJSON *item,int depth,int fmt,printbuffer *p); +static const char *parse_object(cJSON *item,const char *value); +static char *print_object(cJSON *item,int depth,int fmt,printbuffer *p); + +/* Utility to jump whitespace and cr/lf */ +static const char *skip(const char *in) {while (in && *in && (unsigned char)*in<=32) in++; return in;} + +/* Parse an object - create a new root, and populate. */ +cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated) +{ + const char *end=0; + cJSON *c=cJSON_New_Item(); + ep=0; + if (!c) return 0; /* memory fail */ + + end=parse_value(c,skip(value)); + if (!end) {cJSON_Delete(c);return 0;} /* parse failure. ep is set. */ + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}} + if (return_parse_end) *return_parse_end=end; + return c; +} +/* Default options for cJSON_Parse */ +cJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);} + +/* Render a cJSON item/entity/structure to text. */ +char *cJSON_Print(cJSON *item) {return print_value(item,0,1,0);} +char *cJSON_PrintUnformatted(cJSON *item) {return print_value(item,0,0,0);} + +char *cJSON_PrintBuffered(cJSON *item,int prebuffer,int fmt) +{ + printbuffer p; + p.buffer=(char*)cJSON_malloc(prebuffer); + p.length=prebuffer; + p.offset=0; + return print_value(item,0,fmt,&p); + //return p.buffer; +} + + +/* Parser core - when encountering text, process appropriately. */ +static const char *parse_value(cJSON *item,const char *value) +{ + if (!value) return 0; /* Fail on null. */ + if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; } + if (!strncmp(value,"false",5)) { item->type=cJSON_False; return value+5; } + if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1; return value+4; } + if (*value=='\"') { return parse_string(item,value); } + if (*value=='-' || (*value>='0' && *value<='9')) { return parse_number(item,value); } + if (*value=='[') { return parse_array(item,value); } + if (*value=='{') { return parse_object(item,value); } + + ep=value;return 0; /* failure. */ +} + +/* Render a value to text. */ +static char *print_value(cJSON *item,int depth,int fmt,printbuffer *p) +{ + char *out=0; + if (!item) return 0; + if (p) + { + switch ((item->type)&255) + { + case cJSON_NULL: {out=ensure(p,5); if (out) strcpy(out,"null"); break;} + case cJSON_False: {out=ensure(p,6); if (out) strcpy(out,"false"); break;} + case cJSON_True: {out=ensure(p,5); if (out) strcpy(out,"true"); break;} + case cJSON_Number: out=print_number(item,p);break; + case cJSON_String: out=print_string(item,p);break; + case cJSON_Array: out=print_array(item,depth,fmt,p);break; + case cJSON_Object: out=print_object(item,depth,fmt,p);break; + } + } + else + { + switch ((item->type)&255) + { + case cJSON_NULL: out=cJSON_strdup("null"); break; + case cJSON_False: out=cJSON_strdup("false");break; + case cJSON_True: out=cJSON_strdup("true"); break; + case cJSON_Number: out=print_number(item,0);break; + case cJSON_String: out=print_string(item,0);break; + case cJSON_Array: out=print_array(item,depth,fmt,0);break; + case cJSON_Object: out=print_object(item,depth,fmt,0);break; + } + } + return out; +} + +/* Build an array from input text. */ +static const char *parse_array(cJSON *item,const char *value) +{ + cJSON *child; + if (*value!='[') {ep=value;return 0;} /* not an array! */ + + item->type=cJSON_Array; + value=skip(value+1); + if (*value==']') return value+1; /* empty array. */ + + item->child=child=cJSON_New_Item(); + if (!item->child) return 0; /* memory fail */ + value=skip(parse_value(child,skip(value))); /* skip any spacing, get the value. */ + if (!value) return 0; + + while (*value==',') + { + cJSON *new_item; + if ((new_item=cJSON_New_Item()) == NULL) return 0; /* memory fail */ + child->next=new_item;new_item->prev=child;child=new_item; + value=skip(parse_value(child,skip(value+1))); + if (!value) return 0; /* memory fail */ + } + + if (*value==']') return value+1; /* end of array */ + ep=value;return 0; /* malformed. */ +} + +/* Render an array to text */ +static char *print_array(cJSON *item,int depth,int fmt,printbuffer *p) +{ + char **entries; + char *out=0,*ptr,*ret;int len=5; + cJSON *child=item->child; + int numentries=0,i=0,fail=0; + size_t tmplen=0; + + /* How many entries in the array? */ + while (child) numentries++,child=child->next; + /* Explicitly handle numentries==0 */ + if (!numentries) + { + if (p) out=ensure(p,3); + else out=(char*)cJSON_malloc(3); + if (out) strcpy(out,"[]"); + return out; + } + + if (p) + { + /* Compose the output array. */ + i=p->offset; + ptr=ensure(p,1);if (!ptr) return 0; *ptr='['; p->offset++; + child=item->child; + while (child && !fail) + { + print_value(child,depth+1,fmt,p); + p->offset=update(p); + if (child->next) {len=fmt?2:1;ptr=ensure(p,len+1);if (!ptr) return 0;*ptr++=',';if(fmt)*ptr++=' ';*ptr=0;p->offset+=len;} + child=child->next; + } + ptr=ensure(p,2);if (!ptr) return 0; *ptr++=']';*ptr=0; + out=(p->buffer)+i; + } + else + { + /* Allocate an array to hold the values for each */ + entries=(char**)cJSON_malloc(numentries*sizeof(char*)); + if (!entries) return 0; + memset(entries,0,numentries*sizeof(char*)); + /* Retrieve all the results: */ + child=item->child; + while (child && !fail) + { + ret=print_value(child,depth+1,fmt,0); + entries[i++]=ret; + if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1; + child=child->next; + } + + /* If we didn't fail, try to malloc the output string */ + if (!fail) out=(char*)cJSON_malloc(len); + /* If that fails, we fail. */ + if (!out) fail=1; + + /* Handle failure. */ + if (fail) + { + for (i=0;itype=cJSON_Object; + value=skip(value+1); + if (*value=='}') return value+1; /* empty array. */ + + item->child=child=cJSON_New_Item(); + if (!item->child) return 0; + value=skip(parse_string(child,skip(value))); + if (!value) return 0; + child->string=child->valuestring;child->valuestring=0; + if (*value!=':') {ep=value;return 0;} /* fail! */ + value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ + if (!value) return 0; + + while (*value==',') + { + cJSON *new_item; + if ((new_item=cJSON_New_Item()) == NULL) return 0; /* memory fail */ + child->next=new_item;new_item->prev=child;child=new_item; + value=skip(parse_string(child,skip(value+1))); + if (!value) return 0; + child->string=child->valuestring;child->valuestring=0; + if (*value!=':') {ep=value;return 0;} /* fail! */ + value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ + if (!value) return 0; + } + + if (*value=='}') return value+1; /* end of array */ + ep=value;return 0; /* malformed. */ +} + +/* Render an object to text. */ +static char *print_object(cJSON *item,int depth,int fmt,printbuffer *p) +{ + char **entries=0,**names=0; + char *out=0,*ptr,*ret,*str;int len=7,i=0,j; + cJSON *child=item->child; + int numentries=0,fail=0; + size_t tmplen=0; + /* Count the number of entries. */ + while (child) numentries++,child=child->next; + /* Explicitly handle empty object case */ + if (!numentries) + { + if (p) out=ensure(p,fmt?depth+4:3); + else out=(char*)cJSON_malloc(fmt?depth+4:3); + if (!out) return 0; + ptr=out;*ptr++='{'; + if (fmt) {*ptr++='\n';for (i=0;ioffset; + len=fmt?2:1; ptr=ensure(p,len+1); if (!ptr) return 0; + *ptr++='{'; if (fmt) *ptr++='\n'; *ptr=0; p->offset+=len; + child=item->child;depth++; + while (child) + { + if (fmt) + { + ptr=ensure(p,depth); if (!ptr) return 0; + for (j=0;joffset+=depth; + } + print_string_ptr(child->string,p); + p->offset=update(p); + + len=fmt?2:1; + ptr=ensure(p,len); if (!ptr) return 0; + *ptr++=':';if (fmt) *ptr++='\t'; + p->offset+=len; + + print_value(child,depth,fmt,p); + p->offset=update(p); + + len=(fmt?1:0)+(child->next?1:0); + ptr=ensure(p,len+1); if (!ptr) return 0; + if (child->next) *ptr++=','; + if (fmt) *ptr++='\n';*ptr=0; + p->offset+=len; + child=child->next; + } + ptr=ensure(p,fmt?(depth+1):2); if (!ptr) return 0; + if (fmt) for (i=0;ibuffer)+i; + } + else + { + /* Allocate space for the names and the objects */ + entries=(char**)cJSON_malloc(numentries*sizeof(char*)); + if (!entries) return 0; + names=(char**)cJSON_malloc(numentries*sizeof(char*)); + if (!names) {cJSON_free(entries);return 0;} + memset(entries,0,sizeof(char*)*numentries); + memset(names,0,sizeof(char*)*numentries); + + /* Collect all the results into our arrays: */ + child=item->child;depth++;if (fmt) len+=depth; + while (child && !fail) + { + names[i]=str=print_string_ptr(child->string,0); + entries[i++]=ret=print_value(child,depth,fmt,0); + if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1; + child=child->next; + } + + /* Try to allocate the output string */ + if (!fail) out=(char*)cJSON_malloc(len); + if (!out) fail=1; + + /* Handle failure */ + if (fail) + { + for (i=0;ichild;int i=0;while(c)i++,c=c->next;return i;} +cJSON *cJSON_GetArrayItem(cJSON *array,int item) {cJSON *c=array->child; while (c && item>0) item--,c=c->next; return c;} +cJSON *cJSON_GetObjectItem(cJSON *object,const char *string) {cJSON *c=object->child; while (c && cJSON_strcasecmp(c->string,string)) c=c->next; return c;} +int cJSON_HasObjectItem(cJSON *object,const char *string) { + cJSON *c=object->child; + while (c ) + { + if(cJSON_strcasecmp(c->string,string)==0){ + return 1; + } + c=c->next; + } + return 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;} +/* Utility for handling references. */ +static cJSON *create_reference(cJSON *item) {cJSON *ref=cJSON_New_Item();if (!ref) return 0;memcpy(ref,item,sizeof(cJSON));ref->string=0;ref->type|=cJSON_IsReference;ref->next=ref->prev=0;return ref;} + +/* Add item to array/object. */ +void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!item) return; if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}} +void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);} +void cJSON_AddItemToObjectCS(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (!(item->type&cJSON_StringIsConst) && item->string) cJSON_free(item->string);item->string=(char*)string;item->type|=cJSON_StringIsConst;cJSON_AddItemToArray(object,item);} +void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) {cJSON_AddItemToArray(array,create_reference(item));} +void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item) {cJSON_AddItemToObject(object,string,create_reference(item));} + +cJSON *cJSON_DetachItemFromArray(cJSON *array,int which) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return 0; + if (c->prev) c->prev->next=c->next;if (c->next) c->next->prev=c->prev;if (c==array->child) array->child=c->next;c->prev=c->next=0;return c;} +void cJSON_DeleteItemFromArray(cJSON *array,int which) {cJSON_Delete(cJSON_DetachItemFromArray(array,which));} +cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string) {int i=0;cJSON *c=object->child;while (c && cJSON_strcasecmp(c->string,string)) i++,c=c->next;if (c) return cJSON_DetachItemFromArray(object,i);return 0;} +void cJSON_DeleteItemFromObject(cJSON *object,const char *string) {cJSON_Delete(cJSON_DetachItemFromObject(object,string));} + +/* Replace array/object items with new ones. */ +void cJSON_InsertItemInArray(cJSON *array,int which,cJSON *newitem) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) {cJSON_AddItemToArray(array,newitem);return;} + newitem->next=c;newitem->prev=c->prev;c->prev=newitem;if (c==array->child) array->child=newitem; else newitem->prev->next=newitem;} +void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return; + newitem->next=c->next;newitem->prev=c->prev;if (newitem->next) newitem->next->prev=newitem; + if (c==array->child) array->child=newitem; else newitem->prev->next=newitem;c->next=c->prev=0;cJSON_Delete(c);} +void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem){int i=0;cJSON *c=object->child;while(c && cJSON_strcasecmp(c->string,string))i++,c=c->next;if(c){newitem->string=cJSON_strdup(string);cJSON_ReplaceItemInArray(object,i,newitem);}} + +/* Create basic types: */ +cJSON *cJSON_CreateNull(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;} +cJSON *cJSON_CreateTrue(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_True;return item;} +cJSON *cJSON_CreateFalse(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_False;return item;} +cJSON *cJSON_CreateBool(int b) {cJSON *item=cJSON_New_Item();if(item)item->type=b?cJSON_True:cJSON_False;return item;} +cJSON *cJSON_CreateNumber(double num) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int)num;}return item;} +cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;} +cJSON *cJSON_CreateArray(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Array;return item;} +cJSON *cJSON_CreateObject(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Object;return item;} + +/* Create Arrays: */ +cJSON *cJSON_CreateIntArray(const int *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} +cJSON *cJSON_CreateFloatArray(const float *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} +cJSON *cJSON_CreateDoubleArray(const double *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} +cJSON *cJSON_CreateStringArray(const char **strings,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} + +/* Duplication */ +cJSON *cJSON_Duplicate(cJSON *item,int recurse) +{ + cJSON *newitem,*cptr,*nptr=0,*newchild; + /* Bail on bad ptr */ + if (!item) return 0; + /* Create new item */ + newitem=cJSON_New_Item(); + if (!newitem) return 0; + /* Copy over all vars */ + newitem->type=item->type&(~cJSON_IsReference),newitem->valueint=item->valueint,newitem->valuedouble=item->valuedouble; + if (item->valuestring) {newitem->valuestring=cJSON_strdup(item->valuestring); if (!newitem->valuestring) {cJSON_Delete(newitem);return 0;}} + if (item->string) {newitem->string=cJSON_strdup(item->string); if (!newitem->string) {cJSON_Delete(newitem);return 0;}} + /* If non-recursive, then we're done! */ + if (!recurse) return newitem; + /* Walk the ->next chain for the child. */ + cptr=item->child; + while (cptr) + { + newchild=cJSON_Duplicate(cptr,1); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) {cJSON_Delete(newitem);return 0;} + if (nptr) {nptr->next=newchild,newchild->prev=nptr;nptr=newchild;} /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + else {newitem->child=newchild;nptr=newchild;} /* Set newitem->child and move to it */ + cptr=cptr->next; + } + return newitem; +} + +void cJSON_Minify(char *json) +{ + char *into=json; + while (*json) + { + if (*json==' ') json++; + else if (*json=='\t') json++; /* Whitespace characters. */ + else if (*json=='\r') json++; + else if (*json=='\n') json++; + else if (*json=='/' && json[1]=='/') while (*json && *json!='\n') json++; /* double-slash comments, to end of line. */ + else if (*json=='/' && json[1]=='*') {while (*json && !(*json=='*' && json[1]=='/')) json++;json+=2;} /* multiline comments. */ + else if (*json=='\"'){*into++=*json++;while (*json && *json!='\"'){if (*json=='\\') *into++=*json++;*into++=*json++;}*into++=*json++;} /* string literals, which are \" sensitive. */ + else *into++=*json++; /* All other characters. */ + } + *into=0; /* and null-terminate. */ +} diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/src/s2j.c b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/src/s2j.c new file mode 100644 index 0000000..32ff16e --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/plugins/types/struct2json/src/s2j.c @@ -0,0 +1,52 @@ +/* + * This file is part of the struct2json Library. + * + * Copyright (c) 2015, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: Initialize interface for this library. + * Created on: 2015-10-14 + */ + +#include +#include + +S2jHook s2jHook = { + .malloc_fn = malloc, + .free_fn = free, +}; + +/** + * struct2json library initialize + * @note It will initialize cJSON library hooks. + */ +void s2j_init(S2jHook *hook) { + /* initialize cJSON library */ + cJSON_InitHooks((cJSON_Hooks *)hook); + /* initialize hooks */ + if (hook) { + s2jHook.malloc_fn = (hook->malloc_fn) ? hook->malloc_fn : malloc; + s2jHook.free_fn = (hook->free_fn) ? hook->free_fn : free; + } else { + hook->malloc_fn = malloc; + hook->free_fn = free; + } +} diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/port/ef_port.c b/demo/os/nuttx-spiflash/apps/system/easyflash/port/ef_port.c new file mode 100644 index 0000000..e7e354b --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/port/ef_port.c @@ -0,0 +1,232 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2015, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: Portable interface for stm32f10x platform. + * Created on: 2015-01-16 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* default environment variables set for user */ +static const ef_env default_env_set[] = { + {"iap_need_copy_app","0"}, + {"iap_copy_app_size","0"}, + {"stop_in_bootloader","0"}, + {"device_id","1"}, + {"boot_times","0"}, +}; + +static char log_buf[128]; +static pthread_mutex_t env_cache_lock; +static FAR struct mtd_dev_s *mtd_dev; +static const char *devpath="/dev/mtd0"; + +/** + * Flash port for hardware initialize. + * + * @param default_env default ENV set for user + * @param default_env_size default ENV size + * + * @return result + */ +EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size) { + EfErrCode result = EF_NO_ERR; + int fd; + + *default_env = default_env_set; + *default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]); + + pthread_mutex_init(&env_cache_lock, NULL); + + if (bchdev_register("/dev/mtdblock0", devpath, false) < 0){ + result = EF_ENV_INIT_FAILED; + fprintf(stderr, "ERROR: bchdev_register /dev/mtd0 failed: %d\n", result); + return result; + } + + fd = open(devpath, O_RDWR); + if(fd < 0){ + result = EF_ENV_INIT_FAILED; + fprintf(stderr, "ERROR: Failed to open %s: %d\n", devpath, result); + return result; + } + + if (ioctl(fd, MTDIOC_GETMTDDEV, (unsigned long) ((uintptr_t)&mtd_dev)) < 0){ + result = EF_ENV_INIT_FAILED; + fprintf(stderr, "ERROR: Failed to set pintype on %s: %d\n", + devpath, result); + } + close(fd); + return result; +} + +/** + * Read data from flash. + * @note This operation's units is word. + * + * @param addr flash address + * @param buf buffer to store read data + * @param size read bytes size + * + * @return result + */ +EfErrCode ef_port_read(uint32_t addr, uint32_t *buf, size_t size) { + EfErrCode result = EF_NO_ERR; + + if(!MTD_READ(mtd_dev, addr, size, (uint8_t *)buf)){ + result = EF_READ_ERR; + } + + return result; +} + +/** + * Erase data on flash. + * @note This operation is irreversible. + * @note This operation's units is different which on many chips. + * + * @param addr flash address + * @param size erase bytes size + * + * @return result + */ +EfErrCode ef_port_erase(uint32_t addr, size_t size) { + EfErrCode result = EF_NO_ERR; + + /* make sure the start address is a multiple of FLASH_ERASE_MIN_SIZE */ + EF_ASSERT(addr % EF_ERASE_MIN_SIZE == 0); + + if(!MTD_ERASE(mtd_dev, addr/EF_ERASE_MIN_SIZE, size/EF_ERASE_MIN_SIZE)){ + result = EF_ERASE_ERR; + } + + return result; +} + +/** + * Write data to flash. + * @note This operation's units is word. + * @note This operation must after erase. @see flash_erase. + * + * @param addr flash address + * @param buf the write data buffer + * @param size write bytes size + * + * @return result + */ +EfErrCode ef_port_write(uint32_t addr, const uint32_t *buf, size_t size) { + EfErrCode result = EF_NO_ERR; + + if(!MTD_WRITE(mtd_dev, addr, size, (const uint8_t *)buf)){ + result = EF_WRITE_ERR; + } + + return result; +} + +/** + * lock the ENV ram cache + */ +void ef_port_env_lock(void) { + pthread_mutex_lock(&env_cache_lock); +} + +/** + * unlock the ENV ram cache + */ +void ef_port_env_unlock(void) { + pthread_mutex_unlock(&env_cache_lock); +} + + +/** + * This function is print flash debug info. + * + * @param file the file which has call this function + * @param line the line number which has call this function + * @param format output format + * @param ... args + * + */ +void ef_log_debug(const char *file, const long line, const char *format, ...) { + +#ifdef PRINT_DEBUG + + va_list args; + + /* args point to the first variable parameter */ + va_start(args, format); + printf("[Flash](%s:%ld) ", file, line); + /* must use vprintf to print */ + vsprintf(log_buf, format, args); + printf("%s", log_buf); + va_end(args); + +#endif + +} + +/** + * This function is print flash routine info. + * + * @param format output format + * @param ... args + */ +void ef_log_info(const char *format, ...) { + va_list args; + + /* args point to the first variable parameter */ + va_start(args, format); + printf("[Flash]"); + /* must use vprintf to print */ + vsprintf(log_buf, format, args); + printf("%s", log_buf); + va_end(args); +} +/** + * This function is print flash non-package info. + * + * @param format output format + * @param ... args + */ +void ef_print(const char *format, ...) { + va_list args; + + /* args point to the first variable parameter */ + va_start(args, format); + /* must use vprintf to print */ + vsprintf(log_buf, format, args); + printf("%s", log_buf); + va_end(args); +} diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/src/easyflash.c b/demo/os/nuttx-spiflash/apps/system/easyflash/src/easyflash.c new file mode 100644 index 0000000..7455886 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/src/easyflash.c @@ -0,0 +1,109 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2014-2019, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: Initialize interface for this library. + * Created on: 2014-09-09 + */ + +/* + * + * This all Backup Area Flash storage index. All used flash area configure is under here. + * |----------------------------| Storage Size + * | Environment variables area | ENV area size @see ENV_AREA_SIZE + * |----------------------------| + * | Saved log area | Log area size @see LOG_AREA_SIZE + * |----------------------------| + * |(IAP)Downloaded application | IAP already downloaded application, unfixed size + * |----------------------------| + * + * @note all area sizes must be aligned with EF_ERASE_MIN_SIZE + * + * The EasyFlash add the NG (Next Generation) mode start from V4.0. All old mode before V4.0, called LEGACY mode. + * + * - NG (Next Generation) mode is default mode from V4.0. It's easy to settings, only defined the ENV_AREA_SIZE. + * - The LEGACY mode has been DEPRECATED. It is NOT RECOMMENDED to continue using. + * Beacuse it will use ram to buffer the ENV and spend more flash erase times. + * If you want use it please using the V3.X version. + */ + +#include + +#if !defined(EF_START_ADDR) +#error "Please configure backup area start address (in ef_cfg.h)" +#endif + +#if !defined(EF_ERASE_MIN_SIZE) +#error "Please configure minimum size of flash erasure (in ef_cfg.h)" +#endif + +/** + * EasyFlash system initialize. + * + * @return result + */ +EfErrCode easyflash_init(void) { + extern EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size); + extern EfErrCode ef_env_init(ef_env const *default_env, size_t default_env_size); + extern EfErrCode ef_iap_init(void); + extern EfErrCode ef_log_init(void); + + size_t default_env_set_size = 0; + const ef_env *default_env_set; + EfErrCode result = EF_NO_ERR; + static bool init_ok = false; + + if (init_ok) { + return EF_NO_ERR; + } + + result = ef_port_init(&default_env_set, &default_env_set_size); + +#ifdef EF_USING_ENV + if (result == EF_NO_ERR) { + result = ef_env_init(default_env_set, default_env_set_size); + } +#endif + +#ifdef EF_USING_IAP + if (result == EF_NO_ERR) { + result = ef_iap_init(); + } +#endif + +#ifdef EF_USING_LOG + if (result == EF_NO_ERR) { + result = ef_log_init(); + } +#endif + + if (result == EF_NO_ERR) { + init_ok = true; + EF_INFO("EasyFlash V%s is initialize success.\n", EF_SW_VERSION); + } else { + EF_INFO("EasyFlash V%s is initialize fail.\n", EF_SW_VERSION); + } + EF_INFO("You can get the latest version on https://github.com/armink/EasyFlash .\n"); + + return result; +} diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_env.c b/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_env.c new file mode 100644 index 0000000..3bde326 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_env.c @@ -0,0 +1,1841 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2019, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: Environment variables operating interface. This is the Next Generation version. + * Created on: 2019-02-02 + */ + +#include +#include + +#if defined(EF_USING_ENV) && !defined(EF_ENV_USING_LEGACY_MODE) + +#ifndef EF_WRITE_GRAN +#error "Please configure flash write granularity (in ef_cfg.h)" +#endif + +#if EF_WRITE_GRAN != 1 && EF_WRITE_GRAN != 8 && EF_WRITE_GRAN != 32 && EF_WRITE_GRAN != 64 +#error "the write gran can be only setting as 1, 8, 32 and 64" +#endif + +/* magic word(`E`, `F`, `4`, `0`) */ +#define SECTOR_MAGIC_WORD 0x30344645 +/* magic word(`K`, `V`, `4`, `0`) */ +#define ENV_MAGIC_WORD 0x3034564B + +/* the using status sector table length */ +#ifndef USING_SECTOR_TABLE_LEN +#define USING_SECTOR_TABLE_LEN 4 +#endif + +/* the string ENV value buffer size for legacy ef_get_env() function */ +#ifndef EF_STR_ENV_VALUE_MAX_SIZE +#define EF_STR_ENV_VALUE_MAX_SIZE 128 +#endif + +/* the sector remain threshold before full status */ +#ifndef EF_SEC_REMAIN_THRESHOLD +#define EF_SEC_REMAIN_THRESHOLD (ENV_HDR_DATA_SIZE + EF_ENV_NAME_MAX) +#endif + +/* the total remain empty sector threshold before GC */ +#ifndef EF_GC_EMPTY_SEC_THRESHOLD +#define EF_GC_EMPTY_SEC_THRESHOLD 1 +#endif + +/* the ENV cache table size, it will improve ENV search speed when using cache */ +#ifndef EF_ENV_CACHE_TABLE_SIZE +#define EF_ENV_CACHE_TABLE_SIZE 16 +#endif + +/* the sector cache table size, it will improve ENV save speed when using cache */ +#ifndef EF_SECTOR_CACHE_TABLE_SIZE +#define EF_SECTOR_CACHE_TABLE_SIZE 4 +#endif + +#if EF_ENV_CACHE_TABLE_SIZE > 0xFFFF +#error "The ENV cache table size must less than 0xFFFF" +#endif + +#if (EF_ENV_CACHE_TABLE_SIZE > 0) && (EF_SECTOR_CACHE_TABLE_SIZE > 0) +#define EF_ENV_USING_CACHE +#endif + +/* the sector is not combined value */ +#define SECTOR_NOT_COMBINED 0xFFFFFFFF +/* the next address is get failed */ +#define FAILED_ADDR 0xFFFFFFFF + +/* Return the most contiguous size aligned at specified width. RT_ALIGN(13, 4) + * would return 16. + */ +#define EF_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1)) +/* align by write granularity */ +#define EF_WG_ALIGN(size) (EF_ALIGN(size, (EF_WRITE_GRAN + 7)/8)) +/** + * Return the down number of aligned at specified width. RT_ALIGN_DOWN(13, 4) + * would return 12. + */ +#define EF_ALIGN_DOWN(size, align) ((size) & ~((align) - 1)) +/* align down by write granularity */ +#define EF_WG_ALIGN_DOWN(size) (EF_ALIGN_DOWN(size, (EF_WRITE_GRAN + 7)/8)) + +#if (EF_WRITE_GRAN == 1) +#define STATUS_TABLE_SIZE(status_number) ((status_number * EF_WRITE_GRAN + 7)/8) +#else +#define STATUS_TABLE_SIZE(status_number) (((status_number - 1) * EF_WRITE_GRAN + 7)/8) +#endif + +#define STORE_STATUS_TABLE_SIZE STATUS_TABLE_SIZE(SECTOR_STORE_STATUS_NUM) +#define DIRTY_STATUS_TABLE_SIZE STATUS_TABLE_SIZE(SECTOR_DIRTY_STATUS_NUM) +#define ENV_STATUS_TABLE_SIZE STATUS_TABLE_SIZE(ENV_STATUS_NUM) + +#define SECTOR_SIZE EF_ERASE_MIN_SIZE +#define SECTOR_NUM (ENV_AREA_SIZE / (EF_ERASE_MIN_SIZE)) + +#if (SECTOR_NUM < 2) +#error "The sector number must lager then or equal to 2" +#endif + +#if (EF_GC_EMPTY_SEC_THRESHOLD == 0 || EF_GC_EMPTY_SEC_THRESHOLD >= SECTOR_NUM) +#error "There is at least one empty sector for GC." +#endif + +#define SECTOR_HDR_DATA_SIZE (EF_WG_ALIGN(sizeof(struct sector_hdr_data))) +#define SECTOR_DIRTY_OFFSET ((unsigned long)(&((struct sector_hdr_data *)0)->status_table.dirty)) +#define ENV_HDR_DATA_SIZE (EF_WG_ALIGN(sizeof(struct env_hdr_data))) +#define ENV_MAGIC_OFFSET ((unsigned long)(&((struct env_hdr_data *)0)->magic)) +#define ENV_LEN_OFFSET ((unsigned long)(&((struct env_hdr_data *)0)->len)) +#define ENV_NAME_LEN_OFFSET ((unsigned long)(&((struct env_hdr_data *)0)->name_len)) + +#define VER_NUM_ENV_NAME "__ver_num__" + +enum sector_store_status { + SECTOR_STORE_UNUSED, + SECTOR_STORE_EMPTY, + SECTOR_STORE_USING, + SECTOR_STORE_FULL, + SECTOR_STORE_STATUS_NUM, +}; +typedef enum sector_store_status sector_store_status_t; + +enum sector_dirty_status { + SECTOR_DIRTY_UNUSED, + SECTOR_DIRTY_FALSE, + SECTOR_DIRTY_TRUE, + SECTOR_DIRTY_GC, + SECTOR_DIRTY_STATUS_NUM, +}; +typedef enum sector_dirty_status sector_dirty_status_t; + +struct sector_hdr_data { + struct { + uint8_t store[STORE_STATUS_TABLE_SIZE]; /**< sector store status @see sector_store_status_t */ + uint8_t dirty[DIRTY_STATUS_TABLE_SIZE]; /**< sector dirty status @see sector_dirty_status_t */ + } status_table; + uint32_t magic; /**< magic word(`E`, `F`, `4`, `0`) */ + uint32_t combined; /**< the combined next sector number, 0xFFFFFFFF: not combined */ + uint32_t reserved; +}; +typedef struct sector_hdr_data *sector_hdr_data_t; + +struct sector_meta_data { + bool check_ok; /**< sector header check is OK */ + struct { + sector_store_status_t store; /**< sector store status @see sector_store_status_t */ + sector_dirty_status_t dirty; /**< sector dirty status @see sector_dirty_status_t */ + } status; + uint32_t addr; /**< sector start address */ + uint32_t magic; /**< magic word(`E`, `F`, `4`, `0`) */ + uint32_t combined; /**< the combined next sector number, 0xFFFFFFFF: not combined */ + size_t remain; /**< remain size */ + uint32_t empty_env; /**< the next empty ENV node start address */ +}; +typedef struct sector_meta_data *sector_meta_data_t; + +struct env_hdr_data { + uint8_t status_table[ENV_STATUS_TABLE_SIZE]; /**< ENV node status, @see node_status_t */ + uint32_t magic; /**< magic word(`K`, `V`, `4`, `0`) */ + uint32_t len; /**< ENV node total length (header + name + value), must align by EF_WRITE_GRAN */ + uint32_t crc32; /**< ENV node crc32(name_len + data_len + name + value) */ + uint8_t name_len; /**< name length */ + uint32_t value_len; /**< value length */ +}; +typedef struct env_hdr_data *env_hdr_data_t; + +struct env_cache_node { + uint16_t name_crc; /**< ENV name's CRC32 low 16bit value */ + uint16_t active; /**< ENV node access active degree */ + uint32_t addr; /**< ENV node address */ +}; +typedef struct env_cache_node *env_cache_node_t; + +struct sector_cache_node { + uint32_t addr; /**< sector start address */ + uint32_t empty_addr; /**< sector empty address */ +}; +typedef struct sector_cache_node *sector_cache_node_t; + +static void gc_collect(void); + +/* ENV start address in flash */ +static uint32_t env_start_addr = 0; +/* default ENV set, must be initialized by user */ +static ef_env const *default_env_set; +/* default ENV set size, must be initialized by user */ +static size_t default_env_set_size = 0; +/* initialize OK flag */ +static bool init_ok = false; +/* request a GC check */ +static bool gc_request = false; +/* is in recovery check status when first reboot */ +static bool in_recovery_check = false; + +#ifdef EF_ENV_USING_CACHE +/* ENV cache table */ +struct env_cache_node env_cache_table[EF_ENV_CACHE_TABLE_SIZE] = { 0 }; +/* sector cache table, it caching the sector info which status is current using */ +struct sector_cache_node sector_cache_table[EF_SECTOR_CACHE_TABLE_SIZE] = { 0 }; +#endif /* EF_ENV_USING_CACHE */ + +static size_t set_status(uint8_t status_table[], size_t status_num, size_t status_index) +{ + size_t byte_index = ~0UL; + /* + * | write garn | status0 | status1 | status2 | + * --------------------------------------------------------------------------------- + * | 1bit | 0xFF | 0x7F | 0x3F | + * | 8bit | 0xFFFF | 0x00FF | 0x0000 | + * | 32bit | 0xFFFFFFFF FFFFFFFF | 0x00FFFFFF FFFFFFFF | 0x00FFFFFF 00FFFFFF | + */ + memset(status_table, 0xFF, STATUS_TABLE_SIZE(status_num)); + if (status_index > 0) { +#if (EF_WRITE_GRAN == 1) + byte_index = (status_index - 1) / 8; + status_table[byte_index] &= ~(0x80 >> ((status_index - 1) % 8)); +#else + byte_index = (status_index - 1) * (EF_WRITE_GRAN / 8); + status_table[byte_index] = 0x00; +#endif /* EF_WRITE_GRAN == 1 */ + } + + return byte_index; +} + +static size_t get_status(uint8_t status_table[], size_t status_num) +{ + size_t i = 0, status_num_bak = --status_num; + + while (status_num --) { + /* get the first 0 position from end address to start address */ +#if (EF_WRITE_GRAN == 1) + if ((status_table[status_num / 8] & (0x80 >> (status_num % 8))) == 0x00) { + break; + } +#else /* (EF_WRITE_GRAN == 8) || (EF_WRITE_GRAN == 32) || (EF_WRITE_GRAN == 64) */ + if (status_table[status_num * EF_WRITE_GRAN / 8] == 0x00) { + break; + } +#endif /* EF_WRITE_GRAN == 1 */ + i++; + } + + return status_num_bak - i; +} + +static EfErrCode write_status(uint32_t addr, uint8_t status_table[], size_t status_num, size_t status_index) +{ + EfErrCode result = EF_NO_ERR; + size_t byte_index; + + EF_ASSERT(status_index < status_num); + EF_ASSERT(status_table); + + /* set the status first */ + byte_index = set_status(status_table, status_num, status_index); + + /* the first status table value is all 1, so no need to write flash */ + if (byte_index == ~0UL) { + return EF_NO_ERR; + } +#if (EF_WRITE_GRAN == 1) + result = ef_port_write(addr + byte_index, (uint32_t *)&status_table[byte_index], 1); +#else /* (EF_WRITE_GRAN == 8) || (EF_WRITE_GRAN == 32) || (EF_WRITE_GRAN == 64) */ + /* write the status by write granularity + * some flash (like stm32 onchip) NOT supported repeated write before erase */ + result = ef_port_write(addr + byte_index, (uint32_t *) &status_table[byte_index], EF_WRITE_GRAN / 8); +#endif /* EF_WRITE_GRAN == 1 */ + + return result; +} + +static size_t read_status(uint32_t addr, uint8_t status_table[], size_t total_num) +{ + EF_ASSERT(status_table); + + ef_port_read(addr, (uint32_t *) status_table, STATUS_TABLE_SIZE(total_num)); + + return get_status(status_table, total_num); +} + +#ifdef EF_ENV_USING_CACHE +/* + * It's only caching the current using status sector's empty_addr + */ +static void update_sector_cache(uint32_t sec_addr, uint32_t empty_addr) +{ + size_t i, empty_index = EF_SECTOR_CACHE_TABLE_SIZE; + + for (i = 0; i < EF_SECTOR_CACHE_TABLE_SIZE; i++) { + if ((empty_addr > sec_addr) && (empty_addr < sec_addr + SECTOR_SIZE)) { + /* update the sector empty_addr in cache */ + if (sector_cache_table[i].addr == sec_addr) { + sector_cache_table[i].addr = sec_addr; + sector_cache_table[i].empty_addr = empty_addr; + return; + } else if ((sector_cache_table[i].addr == FAILED_ADDR) && (empty_index == EF_SECTOR_CACHE_TABLE_SIZE)) { + empty_index = i; + } + } else if (sector_cache_table[i].addr == sec_addr) { + /* delete the sector which status is not current using */ + sector_cache_table[i].addr = FAILED_ADDR; + return; + } + } + /* add the sector empty_addr to cache */ + if (empty_index < EF_SECTOR_CACHE_TABLE_SIZE) { + sector_cache_table[empty_index].addr = sec_addr; + sector_cache_table[empty_index].empty_addr = empty_addr; + } +} + +/* + * Get sector info from cache. It's return true when cache is hit. + */ +static bool get_sector_from_cache(uint32_t sec_addr, uint32_t *empty_addr) +{ + size_t i; + + for (i = 0; i < EF_SECTOR_CACHE_TABLE_SIZE; i++) { + if (sector_cache_table[i].addr == sec_addr) { + if (empty_addr) { + *empty_addr = sector_cache_table[i].empty_addr; + } + return true; + } + } + + return false; +} + +static void update_env_cache(const char *name, size_t name_len, uint32_t addr) +{ + size_t i, empty_index = EF_ENV_CACHE_TABLE_SIZE, min_activity_index = EF_ENV_CACHE_TABLE_SIZE; + uint16_t name_crc = (uint16_t) (ef_calc_crc32(0, name, name_len) >> 16), min_activity = 0xFFFF; + + for (i = 0; i < EF_ENV_CACHE_TABLE_SIZE; i++) { + if (addr != FAILED_ADDR) { + /* update the ENV address in cache */ + if (env_cache_table[i].name_crc == name_crc) { + env_cache_table[i].addr = addr; + return; + } else if ((env_cache_table[i].addr == FAILED_ADDR) && (empty_index == EF_ENV_CACHE_TABLE_SIZE)) { + empty_index = i; + } else if (env_cache_table[i].addr != FAILED_ADDR) { + if (env_cache_table[i].active > 0) { + env_cache_table[i].active--; + } + if (env_cache_table[i].active < min_activity) { + min_activity_index = i; + min_activity = env_cache_table[i].active; + } + } + } else if (env_cache_table[i].name_crc == name_crc) { + /* delete the ENV */ + env_cache_table[i].addr = FAILED_ADDR; + env_cache_table[i].active = 0; + return; + } + } + /* add the ENV to cache, using LRU (Least Recently Used) like algorithm */ + if (empty_index < EF_ENV_CACHE_TABLE_SIZE) { + env_cache_table[empty_index].addr = addr; + env_cache_table[empty_index].name_crc = name_crc; + env_cache_table[empty_index].active = 0; + } else if (min_activity_index < EF_ENV_CACHE_TABLE_SIZE) { + env_cache_table[min_activity_index].addr = addr; + env_cache_table[min_activity_index].name_crc = name_crc; + env_cache_table[min_activity_index].active = 0; + } +} + +/* + * Get ENV info from cache. It's return true when cache is hit. + */ +static bool get_env_from_cache(const char *name, size_t name_len, uint32_t *addr) +{ + size_t i; + uint16_t name_crc = (uint16_t) (ef_calc_crc32(0, name, name_len) >> 16); + + for (i = 0; i < EF_ENV_CACHE_TABLE_SIZE; i++) { + if ((env_cache_table[i].addr != FAILED_ADDR) && (env_cache_table[i].name_crc == name_crc)) { + char saved_name[EF_ENV_NAME_MAX]; + /* read the ENV name in flash */ + ef_port_read(env_cache_table[i].addr + ENV_HDR_DATA_SIZE, (uint32_t *) saved_name, EF_ENV_NAME_MAX); + if (!strncmp(name, saved_name, name_len)) { + *addr = env_cache_table[i].addr; + if (env_cache_table[i].active >= 0xFFFF - EF_ENV_CACHE_TABLE_SIZE) { + env_cache_table[i].active = 0xFFFF; + } else { + env_cache_table[i].active += EF_ENV_CACHE_TABLE_SIZE; + } + return true; + } + } + } + + return false; +} +#endif /* EF_ENV_USING_CACHE */ + +/* + * find the continue 0xFF flash address to end address + */ +static uint32_t continue_ff_addr(uint32_t start, uint32_t end) +{ + uint8_t buf[32], last_data = 0x00; + size_t i, addr = start, read_size; + + for (; start < end; start += sizeof(buf)) { + if (start + sizeof(buf) < end) { + read_size = sizeof(buf); + } else { + read_size = end - start; + } + ef_port_read(start, (uint32_t *) buf, read_size); + for (i = 0; i < read_size; i++) { + if (last_data != 0xFF && buf[i] == 0xFF) { + addr = start + i; + } + last_data = buf[i]; + } + } + + if (last_data == 0xFF) { + return EF_WG_ALIGN(addr); + } else { + return end; + } +} + +/* + * find the next ENV address by magic word on the flash + */ +static uint32_t find_next_env_addr(uint32_t start, uint32_t end) +{ + uint8_t buf[32]; + uint32_t start_bak = start, i; + uint32_t magic; + +#ifdef EF_ENV_USING_CACHE + uint32_t empty_env; + + if (get_sector_from_cache(EF_ALIGN_DOWN(start, SECTOR_SIZE), &empty_env) && start == empty_env) { + return FAILED_ADDR; + } +#endif /* EF_ENV_USING_CACHE */ + + for (; start < end; start += (sizeof(buf) - sizeof(uint32_t))) { + ef_port_read(start, (uint32_t *) buf, sizeof(buf)); + for (i = 0; i < sizeof(buf) - sizeof(uint32_t) && start + i < end; i++) { +#ifndef EF_BIG_ENDIAN /* Little Endian Order */ + magic = buf[i] + (buf[i + 1] << 8) + (buf[i + 2] << 16) + (buf[i + 3] << 24); +#else /* Big Endian Order */ + magic = buf[i + 3] + (buf[i + 2] << 8) + (buf[i + 1] << 16) + (buf[i] << 24); +#endif + if (magic == ENV_MAGIC_WORD && (start + i - ENV_MAGIC_OFFSET) >= start_bak) { + return start + i - ENV_MAGIC_OFFSET; + } + } + } + + return FAILED_ADDR; +} + +static uint32_t get_next_env_addr(sector_meta_data_t sector, env_node_obj_t pre_env) +{ + uint32_t addr = FAILED_ADDR; + + if (sector->status.store == SECTOR_STORE_EMPTY) { + return FAILED_ADDR; + } + + if (pre_env->addr.start == FAILED_ADDR) { + /* the first ENV address */ + addr = sector->addr + SECTOR_HDR_DATA_SIZE; + } else { + if (pre_env->addr.start <= sector->addr + SECTOR_SIZE) { + if (pre_env->crc_is_ok) { + addr = pre_env->addr.start + pre_env->len; + } else { + /* when pre_env CRC check failed, maybe the flash has error data + * find_next_env_addr after pre_env address */ + addr = pre_env->addr.start + EF_WG_ALIGN(1); + } + /* check and find next ENV address */ + addr = find_next_env_addr(addr, sector->addr + SECTOR_SIZE - SECTOR_HDR_DATA_SIZE); + + if (addr > sector->addr + SECTOR_SIZE || pre_env->len == 0) { + //TODO ��������ģʽ + return FAILED_ADDR; + } + } else { + /* no ENV */ + return FAILED_ADDR; + } + } + + return addr; +} + +static EfErrCode read_env(env_node_obj_t env) +{ + struct env_hdr_data env_hdr; + uint8_t buf[32]; + uint32_t calc_crc32 = 0, crc_data_len, env_name_addr; + EfErrCode result = EF_NO_ERR; + size_t len, size; + /* read ENV header raw data */ + ef_port_read(env->addr.start, (uint32_t *)&env_hdr, sizeof(struct env_hdr_data)); + env->status = (env_status_t) get_status(env_hdr.status_table, ENV_STATUS_NUM); + env->len = env_hdr.len; + + if (env->len == ~0UL || env->len > ENV_AREA_SIZE || env->len < ENV_NAME_LEN_OFFSET) { + /* the ENV length was not write, so reserved the meta data for current ENV */ + env->len = ENV_HDR_DATA_SIZE; + if (env->status != ENV_ERR_HDR) { + env->status = ENV_ERR_HDR; + EF_DEBUG("Error: The ENV @0x%08X length has an error.\n", env->addr.start); + write_status(env->addr.start, env_hdr.status_table, ENV_STATUS_NUM, ENV_ERR_HDR); + } + env->crc_is_ok = false; + return EF_READ_ERR; + } else if (env->len > SECTOR_SIZE - SECTOR_HDR_DATA_SIZE && env->len < ENV_AREA_SIZE) { + //TODO ��������ģʽ������д�볤��û��д������ + EF_ASSERT(0); + } + + /* CRC32 data len(header.name_len + header.value_len + name + value) */ + crc_data_len = env->len - ENV_NAME_LEN_OFFSET; + /* calculate the CRC32 value */ + for (len = 0, size = 0; len < crc_data_len; len += size) { + if (len + sizeof(buf) < crc_data_len) { + size = sizeof(buf); + } else { + size = crc_data_len - len; + } + + ef_port_read(env->addr.start + ENV_NAME_LEN_OFFSET + len, (uint32_t *) buf, EF_WG_ALIGN(size)); + calc_crc32 = ef_calc_crc32(calc_crc32, buf, size); + } + /* check CRC32 */ + if (calc_crc32 != env_hdr.crc32) { + env->crc_is_ok = false; + result = EF_READ_ERR; + } else { + env->crc_is_ok = true; + /* the name is behind aligned ENV header */ + env_name_addr = env->addr.start + ENV_HDR_DATA_SIZE; + ef_port_read(env_name_addr, (uint32_t *) env->name, EF_WG_ALIGN(env_hdr.name_len)); + /* the value is behind aligned name */ + env->addr.value = env_name_addr + EF_WG_ALIGN(env_hdr.name_len); + env->value_len = env_hdr.value_len; + env->name_len = env_hdr.name_len; + } + + return result; +} + +static EfErrCode read_sector_meta_data(uint32_t addr, sector_meta_data_t sector, bool traversal) +{ + EfErrCode result = EF_NO_ERR; + struct sector_hdr_data sec_hdr; + + EF_ASSERT(addr % SECTOR_SIZE == 0); + EF_ASSERT(sector); + + /* read sector header raw data */ + ef_port_read(addr, (uint32_t *)&sec_hdr, sizeof(struct sector_hdr_data)); + + sector->addr = addr; + sector->magic = sec_hdr.magic; + /* check magic word */ + if (sector->magic != SECTOR_MAGIC_WORD) { + sector->check_ok = false; + sector->combined = SECTOR_NOT_COMBINED; + return EF_ENV_INIT_FAILED; + } + sector->check_ok = true; + /* get other sector meta data */ + sector->combined = sec_hdr.combined; + sector->status.store = (sector_store_status_t) get_status(sec_hdr.status_table.store, SECTOR_STORE_STATUS_NUM); + sector->status.dirty = (sector_dirty_status_t) get_status(sec_hdr.status_table.dirty, SECTOR_DIRTY_STATUS_NUM); + /* traversal all ENV and calculate the remain space size */ + if (traversal) { + sector->remain = 0; + sector->empty_env = sector->addr + SECTOR_HDR_DATA_SIZE; + if (sector->status.store == SECTOR_STORE_EMPTY) { + sector->remain = SECTOR_SIZE - SECTOR_HDR_DATA_SIZE; + } else if (sector->status.store == SECTOR_STORE_USING) { + struct env_node_obj env_meta; + +#ifdef EF_ENV_USING_CACHE + if (get_sector_from_cache(addr, §or->empty_env)) { + sector->remain = SECTOR_SIZE - (sector->empty_env - sector->addr); + return result; + } +#endif /* EF_ENV_USING_CACHE */ + + sector->remain = SECTOR_SIZE - SECTOR_HDR_DATA_SIZE; + env_meta.addr.start = FAILED_ADDR; + while ((env_meta.addr.start = get_next_env_addr(sector, &env_meta)) != FAILED_ADDR) { + read_env(&env_meta); + if (!env_meta.crc_is_ok) { + if (env_meta.status != ENV_PRE_WRITE && env_meta.status!= ENV_ERR_HDR) { + EF_INFO("Error: The ENV (@0x%08X) CRC32 check failed!\n", env_meta.addr.start); + sector->remain = 0; + result = EF_READ_ERR; + break; + } + } + sector->empty_env += env_meta.len; + sector->remain -= env_meta.len; + } + /* check the empty ENV address by read continue 0xFF on flash */ + { + uint32_t ff_addr; + + ff_addr = continue_ff_addr(sector->empty_env, sector->addr + SECTOR_SIZE); + /* check the flash data is clean */ + if (sector->empty_env != ff_addr) { + /* update the sector information */ + sector->empty_env = ff_addr; + sector->remain = SECTOR_SIZE - (ff_addr - sector->addr); + } + } + +#ifdef EF_ENV_USING_CACHE + update_sector_cache(sector->addr, sector->empty_env); +#endif + } + } + + return result; +} + +static uint32_t get_next_sector_addr(sector_meta_data_t pre_sec) +{ + uint32_t next_addr; + + if (pre_sec->addr == FAILED_ADDR) { + return env_start_addr; + } else { + /* check ENV sector combined */ + if (pre_sec->combined == SECTOR_NOT_COMBINED) { + next_addr = pre_sec->addr + SECTOR_SIZE; + } else { + next_addr = pre_sec->addr + pre_sec->combined * SECTOR_SIZE; + } + /* check range */ + if (next_addr < env_start_addr + ENV_AREA_SIZE) { + return next_addr; + } else { + /* no sector */ + return FAILED_ADDR; + } + } +} + +static void env_iterator(env_node_obj_t env, void *arg1, void *arg2, + bool (*callback)(env_node_obj_t env, void *arg1, void *arg2)) +{ + struct sector_meta_data sector; + uint32_t sec_addr; + + sector.addr = FAILED_ADDR; + /* search all sectors */ + while ((sec_addr = get_next_sector_addr(§or)) != FAILED_ADDR) { + if (read_sector_meta_data(sec_addr, §or, false) != EF_NO_ERR) { + continue; + } + if (callback == NULL) { + continue; + } + /* sector has ENV */ + if (sector.status.store == SECTOR_STORE_USING || sector.status.store == SECTOR_STORE_FULL) { + env->addr.start = FAILED_ADDR; + /* search all ENV */ + while ((env->addr.start = get_next_env_addr(§or, env)) != FAILED_ADDR) { + read_env(env); + /* iterator is interrupted when callback return true */ + if (callback(env, arg1, arg2)) { + return; + } + } + } + } +} + +static bool find_env_cb(env_node_obj_t env, void *arg1, void *arg2) +{ + const char *key = arg1; + bool *find_ok = arg2; + size_t key_len = strlen(key); + + if (key_len != env->name_len) { + return false; + } + /* check ENV */ + if (env->crc_is_ok && env->status == ENV_WRITE && !strncmp(env->name, key, key_len)) { + *find_ok = true; + return true; + } + return false; +} + +static bool find_env_no_cache(const char *key, env_node_obj_t env) +{ + bool find_ok = false; + + env_iterator(env, (void *)key, &find_ok, find_env_cb); + + return find_ok; +} + +static bool find_env(const char *key, env_node_obj_t env) +{ + bool find_ok = false; + +#ifdef EF_ENV_USING_CACHE + size_t key_len = strlen(key); + + if (get_env_from_cache(key, key_len, &env->addr.start)) { + read_env(env); + return true; + } +#endif /* EF_ENV_USING_CACHE */ + + find_ok = find_env_no_cache(key, env); + +#ifdef EF_ENV_USING_CACHE + if (find_ok) { + update_env_cache(key, key_len, env->addr.start); + } +#endif /* EF_ENV_USING_CACHE */ + + return find_ok; +} + +static bool ef_is_str(uint8_t *value, size_t len) +{ +#define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ') + size_t i; + + for (i = 0; i < len; i++) { + if (!__is_print(value[i])) { + return false; + } + } + return true; +} + +static size_t get_env(const char *key, void *value_buf, size_t buf_len, size_t *value_len) +{ + struct env_node_obj env; + size_t read_len = 0; + + if (find_env(key, &env)) { + if (value_len) { + *value_len = env.value_len; + } + if (buf_len > env.value_len) { + read_len = env.value_len; + } else { + read_len = buf_len; + } + if (value_buf){ + ef_port_read(env.addr.value, (uint32_t *) value_buf, read_len); + } + } else if (value_len) { + *value_len = 0; + } + + return read_len; +} + +/** + * Get a ENV object by key name + * + * @param key ENV name + * @param env ENV object + * + * @return TRUE: find the ENV is OK, else return false + */ +bool ef_get_env_obj(const char *key, env_node_obj_t env) +{ + bool find_ok = false; + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return 0; + } + + /* lock the ENV cache */ + ef_port_env_lock(); + + find_ok = find_env(key, env); + + /* unlock the ENV cache */ + ef_port_env_unlock(); + + return find_ok; +} + +/** + * Get a blob ENV value by key name. + * + * @param key ENV name + * @param value_buf ENV blob buffer + * @param buf_len ENV blob buffer length + * @param saved_value_len return the length of the value saved on the flash, 0: NOT found + * + * @return the actually get size on successful + */ +size_t ef_get_env_blob(const char *key, void *value_buf, size_t buf_len, size_t *saved_value_len) +{ + size_t read_len = 0; + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return 0; + } + + /* lock the ENV cache */ + ef_port_env_lock(); + + read_len = get_env(key, value_buf, buf_len, saved_value_len); + + /* unlock the ENV cache */ + ef_port_env_unlock(); + + return read_len; +} + +/** + * Get an ENV value by key name. + * + * @note this function is NOT supported reentrant + * @note this function is DEPRECATED + * + * @param key ENV name + * + * @return value + */ +char *ef_get_env(const char *key) +{ + static char value[EF_STR_ENV_VALUE_MAX_SIZE + 1]; + size_t get_size; + + if ((get_size = ef_get_env_blob(key, value, EF_STR_ENV_VALUE_MAX_SIZE, NULL)) > 0) { + /* the return value must be string */ + if (ef_is_str((uint8_t *)value, get_size)) { + value[get_size] = '\0'; + return value; + } else { + EF_INFO("Warning: The ENV value isn't string. Could not be returned\n"); + return NULL; + } + } + + return NULL; +} + +/** + * read the ENV value by ENV object + * + * @param env ENV object + * @param value_buf the buffer for store ENV value + * @param buf_len buffer length + * + * @return the actually read size on successful + */ +size_t ef_read_env_value(env_node_obj_t env, uint8_t *value_buf, size_t buf_len) +{ + size_t read_len = 0; + + EF_ASSERT(env); + EF_ASSERT(value_buf); + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return 0; + } + + if (env->crc_is_ok) { + /* lock the ENV cache */ + ef_port_env_lock(); + + if (buf_len > env->value_len) { + read_len = env->value_len; + } else { + read_len = buf_len; + } + + ef_port_read(env->addr.value, (uint32_t *) value_buf, read_len); + /* unlock the ENV cache */ + ef_port_env_unlock(); + } + + return read_len; +} + +static EfErrCode write_env_hdr(uint32_t addr, env_hdr_data_t env_hdr) { + EfErrCode result = EF_NO_ERR; + /* write the status will by write granularity */ + result = write_status(addr, env_hdr->status_table, ENV_STATUS_NUM, ENV_PRE_WRITE); + if (result != EF_NO_ERR) { + return result; + } + /* write other header data */ + result = ef_port_write(addr + ENV_MAGIC_OFFSET, &env_hdr->magic, sizeof(struct env_hdr_data) - ENV_MAGIC_OFFSET); + + return result; +} + +static EfErrCode format_sector(uint32_t addr, uint32_t combined_value) +{ + EfErrCode result = EF_NO_ERR; + struct sector_hdr_data sec_hdr; + + EF_ASSERT(addr % SECTOR_SIZE == 0); + + result = ef_port_erase(addr, SECTOR_SIZE); + if (result == EF_NO_ERR) { + /* initialize the header data */ + memset(&sec_hdr, 0xFF, sizeof(struct sector_hdr_data)); + set_status(sec_hdr.status_table.store, SECTOR_STORE_STATUS_NUM, SECTOR_STORE_EMPTY); + set_status(sec_hdr.status_table.dirty, SECTOR_DIRTY_STATUS_NUM, SECTOR_DIRTY_FALSE); + sec_hdr.magic = SECTOR_MAGIC_WORD; + sec_hdr.combined = combined_value; + sec_hdr.reserved = 0xFFFFFFFF; + /* save the header */ + result = ef_port_write(addr, (uint32_t *)&sec_hdr, sizeof(struct sector_hdr_data)); + +#ifdef EF_ENV_USING_CACHE + /* delete the sector cache */ + update_sector_cache(addr, addr + SECTOR_SIZE); +#endif /* EF_ENV_USING_CACHE */ + } + + return result; +} + +static EfErrCode update_sec_status(sector_meta_data_t sector, size_t new_env_len, bool *is_full) +{ + uint8_t status_table[STORE_STATUS_TABLE_SIZE]; + EfErrCode result = EF_NO_ERR; + /* change the current sector status */ + if (sector->status.store == SECTOR_STORE_EMPTY) { + /* change the sector status to using */ + result = write_status(sector->addr, status_table, SECTOR_STORE_STATUS_NUM, SECTOR_STORE_USING); + } else if (sector->status.store == SECTOR_STORE_USING) { + /* check remain size */ + if (sector->remain < EF_SEC_REMAIN_THRESHOLD || sector->remain - new_env_len < EF_SEC_REMAIN_THRESHOLD) { + /* change the sector status to full */ + result = write_status(sector->addr, status_table, SECTOR_STORE_STATUS_NUM, SECTOR_STORE_FULL); + +#ifdef EF_ENV_USING_CACHE + /* delete the sector cache */ + update_sector_cache(sector->addr, sector->addr + SECTOR_SIZE); +#endif /* EF_ENV_USING_CACHE */ + + if (is_full) { + *is_full = true; + } + } else if (is_full) { + *is_full = false; + } + } + + return result; +} + +static void sector_iterator(sector_meta_data_t sector, sector_store_status_t status, void *arg1, void *arg2, + bool (*callback)(sector_meta_data_t sector, void *arg1, void *arg2), bool traversal_env) { + uint32_t sec_addr; + + /* search all sectors */ + sector->addr = FAILED_ADDR; + while ((sec_addr = get_next_sector_addr(sector)) != FAILED_ADDR) { + read_sector_meta_data(sec_addr, sector, false); + if (status == SECTOR_STORE_UNUSED || status == sector->status.store) { + if (traversal_env) { + read_sector_meta_data(sec_addr, sector, traversal_env); + } + /* iterator is interrupted when callback return true */ + if (callback && callback(sector, arg1, arg2)) { + return; + } + } + } +} + +static bool sector_statistics_cb(sector_meta_data_t sector, void *arg1, void *arg2) +{ + size_t *empty_sector = arg1, *using_sector = arg2; + + if (sector->check_ok && sector->status.store == SECTOR_STORE_EMPTY) { + (*empty_sector)++; + } else if (sector->check_ok && sector->status.store == SECTOR_STORE_USING) { + (*using_sector)++; + } + + return false; +} + +static bool alloc_env_cb(sector_meta_data_t sector, void *arg1, void *arg2) +{ + size_t *env_size = arg1; + uint32_t *empty_env = arg2; + + /* 1. sector has space + * 2. the NO dirty sector + * 3. the dirty sector only when the gc_request is false */ + if (sector->check_ok && sector->remain > *env_size + && ((sector->status.dirty == SECTOR_DIRTY_FALSE) + || (sector->status.dirty == SECTOR_DIRTY_TRUE && !gc_request))) { + *empty_env = sector->empty_env; + return true; + } + + return false; +} + +static uint32_t alloc_env(sector_meta_data_t sector, size_t env_size) +{ + uint32_t empty_env = FAILED_ADDR; + size_t empty_sector = 0, using_sector = 0; + + /* sector status statistics */ + sector_iterator(sector, SECTOR_STORE_UNUSED, &empty_sector, &using_sector, sector_statistics_cb, false); + if (using_sector > 0) { + /* alloc the ENV from the using status sector first */ + sector_iterator(sector, SECTOR_STORE_USING, &env_size, &empty_env, alloc_env_cb, true); + } + if (empty_sector > 0 && empty_env == FAILED_ADDR) { + if (empty_sector > EF_GC_EMPTY_SEC_THRESHOLD || gc_request) { + sector_iterator(sector, SECTOR_STORE_EMPTY, &env_size, &empty_env, alloc_env_cb, true); + } else { + /* no space for new ENV now will GC and retry */ + EF_DEBUG("Trigger a GC check after alloc ENV failed.\n"); + gc_request = true; + } + } + + return empty_env; +} + +static EfErrCode del_env(const char *key, env_node_obj_t old_env, bool complete_del) { + EfErrCode result = EF_NO_ERR; + uint32_t dirty_status_addr; + static bool last_is_complete_del = false; + +#if (ENV_STATUS_TABLE_SIZE >= DIRTY_STATUS_TABLE_SIZE) + uint8_t status_table[ENV_STATUS_TABLE_SIZE]; +#else + uint8_t status_table[DIRTY_STATUS_TABLE_SIZE]; +#endif + + /* need find ENV */ + if (!old_env) { + struct env_node_obj env; + /* find ENV */ + if (find_env(key, &env)) { + old_env = &env; + } else { + EF_DEBUG("Not found '%s' in ENV.\n", key); + return EF_ENV_NAME_ERR; + } + } + /* change and save the new status */ + if (!complete_del) { + result = write_status(old_env->addr.start, status_table, ENV_STATUS_NUM, ENV_PRE_DELETE); + last_is_complete_del = true; + } else { + result = write_status(old_env->addr.start, status_table, ENV_STATUS_NUM, ENV_DELETED); + + if (!last_is_complete_del && result == EF_NO_ERR) { +#ifdef EF_ENV_USING_CACHE + /* delete the ENV in flash and cache */ + if (key != NULL) { + /* when using del_env(key, NULL, true) or del_env(key, env, true) in ef_del_env() and set_env() */ + update_env_cache(key, strlen(key), FAILED_ADDR); + } else if (old_env != NULL) { + /* when using del_env(NULL, env, true) in move_env() */ + update_env_cache(old_env->name, old_env->name_len, FAILED_ADDR); + } +#endif /* EF_ENV_USING_CACHE */ + } + + last_is_complete_del = false; + } + + dirty_status_addr = EF_ALIGN_DOWN(old_env->addr.start, SECTOR_SIZE) + SECTOR_DIRTY_OFFSET; + /* read and change the sector dirty status */ + if (result == EF_NO_ERR + && read_status(dirty_status_addr, status_table, SECTOR_DIRTY_STATUS_NUM) == SECTOR_DIRTY_FALSE) { + result = write_status(dirty_status_addr, status_table, SECTOR_DIRTY_STATUS_NUM, SECTOR_DIRTY_TRUE); + } + + return result; +} + +/* + * move the ENV to new space + */ +static EfErrCode move_env(env_node_obj_t env) +{ + EfErrCode result = EF_NO_ERR; + uint8_t status_table[ENV_STATUS_TABLE_SIZE]; + uint32_t env_addr; + struct sector_meta_data sector; + + /* prepare to delete the current ENV */ + if (env->status == ENV_WRITE) { + del_env(NULL, env, false); + } + + if ((env_addr = alloc_env(§or, env->len)) != FAILED_ADDR) { + if (in_recovery_check) { + struct env_node_obj env_bak; + char name[EF_ENV_NAME_MAX + 1] = { 0 }; + strncpy(name, env->name, env->name_len); + /* check the ENV in flash is already create success */ + if (find_env_no_cache(name, &env_bak)) { + /* already create success, don't need to duplicate */ + result = EF_NO_ERR; + goto __exit; + } + } + } else { + return EF_ENV_FULL; + } + /* start move the ENV */ + { + uint8_t buf[32]; + size_t len, size, env_len = env->len; + + /* update the new ENV sector status first */ + update_sec_status(§or, env->len, NULL); + + write_status(env_addr, status_table, ENV_STATUS_NUM, ENV_PRE_WRITE); + env_len -= ENV_MAGIC_OFFSET; + for (len = 0, size = 0; len < env_len; len += size) { + if (len + sizeof(buf) < env_len) { + size = sizeof(buf); + } else { + size = env_len - len; + } + ef_port_read(env->addr.start + ENV_MAGIC_OFFSET + len, (uint32_t *) buf, EF_WG_ALIGN(size)); + result = ef_port_write(env_addr + ENV_MAGIC_OFFSET + len, (uint32_t *) buf, size); + } + write_status(env_addr, status_table, ENV_STATUS_NUM, ENV_WRITE); + +#ifdef EF_ENV_USING_CACHE + update_sector_cache(EF_ALIGN_DOWN(env_addr, SECTOR_SIZE), + env_addr + ENV_HDR_DATA_SIZE + EF_WG_ALIGN(env->name_len) + EF_WG_ALIGN(env->value_len)); + update_env_cache(env->name, env->name_len, env_addr); +#endif /* EF_ENV_USING_CACHE */ + } + + EF_DEBUG("Moved the ENV (%.*s) from 0x%08X to 0x%08X.\n", env->name_len, env->name, env->addr.start, env_addr); + +__exit: + del_env(NULL, env, true); + + return result; +} + +static uint32_t new_env(sector_meta_data_t sector, size_t env_size) +{ + bool already_gc = false; + uint32_t empty_env = FAILED_ADDR; + +__retry: + + if ((empty_env = alloc_env(sector, env_size)) == FAILED_ADDR && gc_request && !already_gc) { + EF_DEBUG("Warning: Alloc an ENV (size %d) failed when new ENV. Now will GC then retry.\n", env_size); + gc_collect(); + already_gc = true; + goto __retry; + } + + return empty_env; +} + +static uint32_t new_env_by_kv(sector_meta_data_t sector, size_t key_len, size_t buf_len) +{ + size_t env_len = ENV_HDR_DATA_SIZE + EF_WG_ALIGN(key_len) + EF_WG_ALIGN(buf_len); + + return new_env(sector, env_len); +} + +static bool gc_check_cb(sector_meta_data_t sector, void *arg1, void *arg2) +{ + size_t *empty_sec = arg1; + + if (sector->check_ok) { + *empty_sec = *empty_sec + 1; + } + + return false; + +} + +static bool do_gc(sector_meta_data_t sector, void *arg1, void *arg2) +{ + struct env_node_obj env; + + if (sector->check_ok && (sector->status.dirty == SECTOR_DIRTY_TRUE || sector->status.dirty == SECTOR_DIRTY_GC)) { + uint8_t status_table[DIRTY_STATUS_TABLE_SIZE]; + /* change the sector status to GC */ + write_status(sector->addr + SECTOR_DIRTY_OFFSET, status_table, SECTOR_DIRTY_STATUS_NUM, SECTOR_DIRTY_GC); + /* search all ENV */ + env.addr.start = FAILED_ADDR; + while ((env.addr.start = get_next_env_addr(sector, &env)) != FAILED_ADDR) { + read_env(&env); + if (env.crc_is_ok && (env.status == ENV_WRITE || env.status == ENV_PRE_DELETE)) { + /* move the ENV to new space */ + if (move_env(&env) != EF_NO_ERR) { + EF_DEBUG("Error: Moved the ENV (%.*s) for GC failed.\n", env.name_len, env.name); + } + } + } + format_sector(sector->addr, SECTOR_NOT_COMBINED); + EF_DEBUG("Collect a sector @0x%08X\n", sector->addr); + } + + return false; +} + +/* + * The GC will be triggered on the following scene: + * 1. alloc an ENV when the flash not has enough space + * 2. write an ENV then the flash not has enough space + */ +static void gc_collect(void) +{ + struct sector_meta_data sector; + size_t empty_sec = 0; + + /* GC check the empty sector number */ + sector_iterator(§or, SECTOR_STORE_EMPTY, &empty_sec, NULL, gc_check_cb, false); + + /* do GC collect */ + EF_DEBUG("The remain empty sector is %d, GC threshold is %d.\n", empty_sec, EF_GC_EMPTY_SEC_THRESHOLD); + if (empty_sec <= EF_GC_EMPTY_SEC_THRESHOLD) { + sector_iterator(§or, SECTOR_STORE_UNUSED, NULL, NULL, do_gc, false); + } + + gc_request = false; +} + +static EfErrCode align_write(uint32_t addr, const uint32_t *buf, size_t size) +{ + EfErrCode result = EF_NO_ERR; + size_t align_remain; + +#if (EF_WRITE_GRAN / 8 > 0) + uint8_t align_data[EF_WRITE_GRAN / 8]; + size_t align_data_size = sizeof(align_data); +#else + /* For compatibility with C89 */ + uint8_t align_data_u8, *align_data = &align_data_u8; + size_t align_data_size = 1; +#endif + + memset(align_data, 0xFF, align_data_size); + align_remain = EF_WG_ALIGN_DOWN(size);//use align_remain temporary to save aligned size. + + if(align_remain > 0){//it may be 0 in this function. + result = ef_port_write(addr, buf, align_remain); + } + + align_remain = size - align_remain; + if (result == EF_NO_ERR && align_remain) { + memcpy(align_data, (uint8_t *)buf + EF_WG_ALIGN_DOWN(size), align_remain); + result = ef_port_write(addr + EF_WG_ALIGN_DOWN(size), (uint32_t *) align_data, align_data_size); + } + + return result; +} + +static EfErrCode create_env_blob(sector_meta_data_t sector, const char *key, const void *value, size_t len) +{ + EfErrCode result = EF_NO_ERR; + struct env_hdr_data env_hdr; + bool is_full = false; + uint32_t env_addr = sector->empty_env; + + if (strlen(key) > EF_ENV_NAME_MAX) { + EF_INFO("Error: The ENV name length is more than %d\n", EF_ENV_NAME_MAX); + return EF_ENV_NAME_ERR; + } + + memset(&env_hdr, 0xFF, sizeof(struct env_hdr_data)); + env_hdr.magic = ENV_MAGIC_WORD; + env_hdr.name_len = strlen(key); + env_hdr.value_len = len; + env_hdr.len = ENV_HDR_DATA_SIZE + EF_WG_ALIGN(env_hdr.name_len) + EF_WG_ALIGN(env_hdr.value_len); + + if (env_hdr.len > SECTOR_SIZE - SECTOR_HDR_DATA_SIZE) { + EF_INFO("Error: The ENV size is too big\n"); + return EF_ENV_FULL; + } + + if (env_addr != FAILED_ADDR || (env_addr = new_env(sector, env_hdr.len)) != FAILED_ADDR) { + size_t align_remain; + /* update the sector status */ + if (result == EF_NO_ERR) { + result = update_sec_status(sector, env_hdr.len, &is_full); + } + if (result == EF_NO_ERR) { + uint8_t ff = 0xFF; + /* start calculate CRC32 */ + env_hdr.crc32 = ef_calc_crc32(0, &env_hdr.name_len, ENV_HDR_DATA_SIZE - ENV_NAME_LEN_OFFSET); + env_hdr.crc32 = ef_calc_crc32(env_hdr.crc32, key, env_hdr.name_len); + align_remain = EF_WG_ALIGN(env_hdr.name_len) - env_hdr.name_len; + while (align_remain--) { + env_hdr.crc32 = ef_calc_crc32(env_hdr.crc32, &ff, 1); + } + env_hdr.crc32 = ef_calc_crc32(env_hdr.crc32, value, env_hdr.value_len); + align_remain = EF_WG_ALIGN(env_hdr.value_len) - env_hdr.value_len; + while (align_remain--) { + env_hdr.crc32 = ef_calc_crc32(env_hdr.crc32, &ff, 1); + } + /* write ENV header data */ + result = write_env_hdr(env_addr, &env_hdr); + + } + /* write key name */ + if (result == EF_NO_ERR) { + result = align_write(env_addr + ENV_HDR_DATA_SIZE, (uint32_t *) key, env_hdr.name_len); + +#ifdef EF_ENV_USING_CACHE + if (!is_full) { + update_sector_cache(sector->addr, + env_addr + ENV_HDR_DATA_SIZE + EF_WG_ALIGN(env_hdr.name_len) + EF_WG_ALIGN(env_hdr.value_len)); + } + update_env_cache(key, env_hdr.name_len, env_addr); +#endif /* EF_ENV_USING_CACHE */ + } + /* write value */ + if (result == EF_NO_ERR) { + result = align_write(env_addr + ENV_HDR_DATA_SIZE + EF_WG_ALIGN(env_hdr.name_len), value, + env_hdr.value_len); + } + /* change the ENV status to ENV_WRITE */ + if (result == EF_NO_ERR) { + result = write_status(env_addr, env_hdr.status_table, ENV_STATUS_NUM, ENV_WRITE); + } + /* trigger GC collect when current sector is full */ + if (result == EF_NO_ERR && is_full) { + EF_DEBUG("Trigger a GC check after created ENV.\n"); + gc_request = true; + } + } else { + result = EF_ENV_FULL; + } + + return result; +} + +/** + * Delete an ENV. + * + * @param key ENV name + * + * @return result + */ +EfErrCode ef_del_env(const char *key) +{ + EfErrCode result = EF_NO_ERR; + + if (!init_ok) { + EF_INFO("Error: ENV isn't initialize OK.\n"); + return EF_ENV_INIT_FAILED; + } + + /* lock the ENV cache */ + ef_port_env_lock(); + + result = del_env(key, NULL, true); + + /* unlock the ENV cache */ + ef_port_env_unlock(); + + return result; +} + +/** + * The same to ef_del_env on this mode + * It's compatibility with older versions (less then V4.0). + * + * @note this function is DEPRECATED + * + * @param key ENV name + * + * @return result + */ +EfErrCode ef_del_and_save_env(const char *key) +{ + return ef_del_env(key); +} + +static EfErrCode set_env(const char *key, const void *value_buf, size_t buf_len) +{ + EfErrCode result = EF_NO_ERR; + static struct env_node_obj env; + static struct sector_meta_data sector; + bool env_is_found = false; + + if (value_buf == NULL) { + result = del_env(key, NULL, true); + } else { + /* make sure the flash has enough space */ + if (new_env_by_kv(§or, strlen(key), buf_len) == FAILED_ADDR) { + return EF_ENV_FULL; + } + env_is_found = find_env(key, &env); + /* prepare to delete the old ENV */ + if (env_is_found) { + result = del_env(key, &env, false); + } + /* create the new ENV */ + if (result == EF_NO_ERR) { + result = create_env_blob(§or, key, value_buf, buf_len); + } + /* delete the old ENV */ + if (env_is_found && result == EF_NO_ERR) { + result = del_env(key, &env, true); + } + /* process the GC after set ENV */ + if (gc_request) { + gc_collect(); + } + } + + return result; +} + +/** + * Set a blob ENV. If it value is NULL, delete it. + * If not find it in flash, then create it. + * + * @param key ENV name + * @param value ENV value + * @param len ENV value length + * + * @return result + */ +EfErrCode ef_set_env_blob(const char *key, const void *value_buf, size_t buf_len) +{ + EfErrCode result = EF_NO_ERR; + + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return EF_ENV_INIT_FAILED; + } + + /* lock the ENV cache */ + ef_port_env_lock(); + + result = set_env(key, value_buf, buf_len); + + /* unlock the ENV cache */ + ef_port_env_unlock(); + + return result; +} + +/** + * Set a string ENV. If it value is NULL, delete it. + * If not find it in flash, then create it. + * + * @param key ENV name + * @param value ENV value + * + * @return result + */ +EfErrCode ef_set_env(const char *key, const char *value) +{ + return ef_set_env_blob(key, value, strlen(value)); +} + +/** + * The same to ef_set_env on this mode. + * It's compatibility with older versions (less then V4.0). + * + * @note this function is DEPRECATED + * + * @param key ENV name + * @param value ENV value + * + * @return result + */ +EfErrCode ef_set_and_save_env(const char *key, const char *value) +{ + return ef_set_env_blob(key, value, strlen(value)); +} + +/** + * Save ENV to flash. + * + * @note this function is DEPRECATED + */ +EfErrCode ef_save_env(void) +{ + /* do nothing not cur mode */ + return EF_NO_ERR; +} + +/** + * ENV set default. + * + * @return result + */ +EfErrCode ef_env_set_default(void) +{ + EfErrCode result = EF_NO_ERR; + uint32_t addr, i, value_len; + struct sector_meta_data sector; + + EF_ASSERT(default_env_set); + EF_ASSERT(default_env_set_size); + + /* lock the ENV cache */ + ef_port_env_lock(); + /* format all sectors */ + for (addr = env_start_addr; addr < env_start_addr + ENV_AREA_SIZE; addr += SECTOR_SIZE) { + result = format_sector(addr, SECTOR_NOT_COMBINED); + if (result != EF_NO_ERR) { + goto __exit; + } + } + /* create default ENV */ + for (i = 0; i < default_env_set_size; i++) { + /* It seems to be a string when value length is 0. + * This mechanism is for compatibility with older versions (less then V4.0). */ + if (default_env_set[i].value_len == 0) { + value_len = strlen(default_env_set[i].value); + } else { + value_len = default_env_set[i].value_len; + } + sector.empty_env = FAILED_ADDR; + create_env_blob(§or, default_env_set[i].key, default_env_set[i].value, value_len); + if (result != EF_NO_ERR) { + goto __exit; + } + } + +__exit: + /* unlock the ENV cache */ + ef_port_env_unlock(); + + return result; +} + +static bool print_env_cb(env_node_obj_t env, void *arg1, void *arg2) +{ + bool value_is_str = true, print_value = false; + size_t *using_size = arg1; + + if (env->crc_is_ok) { + /* calculate the total using flash size */ + *using_size += env->len; + /* check ENV */ + if (env->status == ENV_WRITE) { + ef_print("%.*s=", env->name_len, env->name); + + if (env->value_len < EF_STR_ENV_VALUE_MAX_SIZE ) { + uint8_t buf[32]; + size_t len, size; +__reload: + /* check the value is string */ + for (len = 0, size = 0; len < env->value_len; len += size) { + if (len + sizeof(buf) < env->value_len) { + size = sizeof(buf); + } else { + size = env->value_len - len; + } + ef_port_read(env->addr.value + len, (uint32_t *) buf, EF_WG_ALIGN(size)); + if (print_value) { + ef_print("%.*s", size, buf); + } else if (!ef_is_str(buf, size)) { + value_is_str = false; + break; + } + } + } else { + value_is_str = false; + } + if (value_is_str && !print_value) { + print_value = true; + goto __reload; + } else if (!value_is_str) { + ef_print("blob @0x%08X %dbytes", env->addr.value, env->value_len); + } + ef_print("\n"); + } + } + + return false; +} + + +/** + * Print ENV. + */ +void ef_print_env(void) +{ + struct env_node_obj env; + size_t using_size = 0; + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return; + } + + /* lock the ENV cache */ + ef_port_env_lock(); + + env_iterator(&env, &using_size, NULL, print_env_cb); + + ef_print("\nmode: next generation\n"); + ef_print("size: %lu/%lu bytes.\n", using_size + (SECTOR_NUM - EF_GC_EMPTY_SEC_THRESHOLD) * SECTOR_HDR_DATA_SIZE, + ENV_AREA_SIZE - SECTOR_SIZE * EF_GC_EMPTY_SEC_THRESHOLD); + + /* unlock the ENV cache */ + ef_port_env_unlock(); +} + +#ifdef EF_ENV_AUTO_UPDATE +/* + * Auto update ENV to latest default when current EF_ENV_VER_NUM is changed. + */ +static void env_auto_update(void) +{ + size_t saved_ver_num, setting_ver_num = EF_ENV_VER_NUM; + + if (get_env(VER_NUM_ENV_NAME, &saved_ver_num, sizeof(size_t), NULL) > 0) { + /* check version number */ + if (saved_ver_num != setting_ver_num) { + struct env_node_obj env; + size_t i, value_len; + struct sector_meta_data sector; + EF_DEBUG("Update the ENV from version %d to %d.\n", saved_ver_num, setting_ver_num); + for (i = 0; i < default_env_set_size; i++) { + /* add a new ENV when it's not found */ + if (!find_env(default_env_set[i].key, &env)) { + /* It seems to be a string when value length is 0. + * This mechanism is for compatibility with older versions (less then V4.0). */ + if (default_env_set[i].value_len == 0) { + value_len = strlen(default_env_set[i].value); + } else { + value_len = default_env_set[i].value_len; + } + sector.empty_env = FAILED_ADDR; + create_env_blob(§or, default_env_set[i].key, default_env_set[i].value, value_len); + } + } + } else { + /* version number not changed now return */ + return; + } + } + + set_env(VER_NUM_ENV_NAME, &setting_ver_num, sizeof(size_t)); +} +#endif /* EF_ENV_AUTO_UPDATE */ + +static bool check_sec_hdr_cb(sector_meta_data_t sector, void *arg1, void *arg2) +{ + if (!sector->check_ok) { + size_t *failed_count = arg1; + + EF_INFO("Warning: Sector header check failed. Format this sector (0x%08x).\n", sector->addr); + (*failed_count) ++; + format_sector(sector->addr, SECTOR_NOT_COMBINED); + } + + return false; +} + +static bool check_and_recovery_gc_cb(sector_meta_data_t sector, void *arg1, void *arg2) +{ + if (sector->check_ok && sector->status.dirty == SECTOR_DIRTY_GC) { + /* make sure the GC request flag to true */ + gc_request = true; + /* resume the GC operate */ + gc_collect(); + } + + return false; +} + +static bool check_and_recovery_env_cb(env_node_obj_t env, void *arg1, void *arg2) +{ + /* recovery the prepare deleted ENV */ + if (env->crc_is_ok && env->status == ENV_PRE_DELETE) { + EF_INFO("Found an ENV (%.*s) which has changed value failed. Now will recovery it.\n", env->name_len, env->name); + /* recovery the old ENV */ + if (move_env(env) == EF_NO_ERR) { + EF_DEBUG("Recovery the ENV successful.\n"); + } else { + EF_DEBUG("Warning: Moved an ENV (size %d) failed when recovery. Now will GC then retry.\n", env->len); + return true; + } + } else if (env->status == ENV_PRE_WRITE) { + uint8_t status_table[ENV_STATUS_TABLE_SIZE]; + /* the ENV has not write finish, change the status to error */ + //TODO �����쳣������״̬װ��ͼ + write_status(env->addr.start, status_table, ENV_STATUS_NUM, ENV_ERR_HDR); + return true; + } + + return false; +} + +/** + * Check and load the flash ENV meta data. + * + * @return result + */ +EfErrCode ef_load_env(void) +{ + EfErrCode result = EF_NO_ERR; + struct env_node_obj env; + struct sector_meta_data sector; + size_t check_failed_count = 0; + + in_recovery_check = true; + /* check all sector header */ + sector_iterator(§or, SECTOR_STORE_UNUSED, &check_failed_count, NULL, check_sec_hdr_cb, false); + /* all sector header check failed */ + if (check_failed_count == SECTOR_NUM) { + EF_INFO("Warning: All sector header check failed. Set it to default.\n"); + ef_env_set_default(); + } + + /* lock the ENV cache */ + ef_port_env_lock(); + /* check all sector header for recovery GC */ + sector_iterator(§or, SECTOR_STORE_UNUSED, NULL, NULL, check_and_recovery_gc_cb, false); + +__retry: + /* check all ENV for recovery */ + env_iterator(&env, NULL, NULL, check_and_recovery_env_cb); + if (gc_request) { + gc_collect(); + goto __retry; + } + + in_recovery_check = false; + + /* unlock the ENV cache */ + ef_port_env_unlock(); + + return result; +} + +/** + * Flash ENV initialize. + * + * @param default_env default ENV set for user + * @param default_env_size default ENV set size + * + * @return result + */ +EfErrCode ef_env_init(ef_env const *default_env, size_t default_env_size) { + EfErrCode result = EF_NO_ERR; + +#ifdef EF_ENV_USING_CACHE + size_t i; +#endif + + EF_ASSERT(default_env); + EF_ASSERT(ENV_AREA_SIZE); + /* must be aligned with erase_min_size */ + EF_ASSERT(ENV_AREA_SIZE % EF_ERASE_MIN_SIZE == 0); + /* sector number must be greater than or equal to 2 */ + EF_ASSERT(SECTOR_NUM >= 2); + /* must be aligned with write granularity */ + EF_ASSERT((EF_STR_ENV_VALUE_MAX_SIZE * 8) % EF_WRITE_GRAN == 0); + + if (init_ok) { + return EF_NO_ERR; + } + +#ifdef EF_ENV_USING_CACHE + for (i = 0; i < EF_SECTOR_CACHE_TABLE_SIZE; i++) { + sector_cache_table[i].addr = FAILED_ADDR; + } + for (i = 0; i < EF_ENV_CACHE_TABLE_SIZE; i++) { + env_cache_table[i].addr = FAILED_ADDR; + } +#endif /* EF_ENV_USING_CACHE */ + + env_start_addr = EF_START_ADDR; + default_env_set = default_env; + default_env_set_size = default_env_size; + + EF_DEBUG("ENV start address is 0x%08X, size is %d bytes.\n", EF_START_ADDR, ENV_AREA_SIZE); + + result = ef_load_env(); + +#ifdef EF_ENV_AUTO_UPDATE + if (result == EF_NO_ERR) { + env_auto_update(); + } +#endif + + if (result == EF_NO_ERR) { + init_ok = true; + } + + return result; +} + +#endif /* defined(EF_USING_ENV) && !defined(EF_ENV_USING_LEGACY_MODE) */ diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_env_legacy.c b/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_env_legacy.c new file mode 100644 index 0000000..c71a714 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_env_legacy.c @@ -0,0 +1,922 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2014-2018, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: Environment variables operating interface. (normal mode) + * Created on: 2014-10-06 + */ + +#include +#include +#include + +#if defined(EF_USING_ENV) && defined(EF_ENV_USING_LEGACY_MODE) + +#ifndef EF_ENV_USING_WL_MODE + +#if defined(EF_USING_ENV) && (!defined(ENV_USER_SETTING_SIZE) || !defined(ENV_AREA_SIZE)) +#error "Please configure user setting ENV size or ENV area size (in ef_cfg.h)" +#endif + +/** + * ENV area has 2 sections + * 1. System section + * It storages ENV parameters. (Units: Word) + * 2. Data section + * It storages all ENV. Storage format is key=value\0. + * All ENV must be 4 bytes alignment. The remaining part must fill '\0'. + * + * @note Word = 4 Bytes in this file + * @note When using power fail safeguard mode, it has two ENV areas(Area0, Area1). + */ + +/* flash ENV parameters index and size in system section */ +enum { + /* data section ENV end address index in system section */ + ENV_PARAM_INDEX_END_ADDR = 0, + +#ifdef EF_ENV_USING_PFS_MODE + /* saved count for ENV area */ + ENV_PARAM_INDEX_SAVED_COUNT, +#endif + +#ifdef EF_ENV_AUTO_UPDATE + /* current version number for ENV */ + ENV_PARAM_INDEX_VER_NUM, +#endif + + /* data section CRC32 code index in system section */ + ENV_PARAM_INDEX_DATA_CRC, + /* flash ENV parameters word size */ + ENV_PARAM_WORD_SIZE, + /* flash ENV parameters byte size */ + ENV_PARAM_BYTE_SIZE = ENV_PARAM_WORD_SIZE * 4, +}; + +/* default ENV set, must be initialized by user */ +static ef_env const *default_env_set; +/* default ENV set size, must be initialized by user */ +static size_t default_env_set_size = 0; +/* ENV ram cache */ +static uint32_t env_cache[ENV_USER_SETTING_SIZE / 4] = { 0 }; +/* ENV start address in flash */ +static uint32_t env_start_addr = 0; +/* ENV ram cache has changed when ENV created, deleted and changed value. */ +static bool env_cache_changed = false; +/* initialize OK flag */ +static bool init_ok = false; + +#ifdef EF_ENV_USING_PFS_MODE +/* current load ENV area address */ +static uint32_t cur_load_area_addr = 0; +/* next save ENV area address */ +static uint32_t next_save_area_addr = 0; +#endif + +static uint32_t get_env_system_addr(void); +static uint32_t get_env_data_addr(void); +static uint32_t get_env_end_addr(void); +static void set_env_end_addr(uint32_t end_addr); +static EfErrCode write_env(const char *key, const char *value); +static char *find_env(const char *key); +static EfErrCode del_env(const char *key); +static size_t get_env_data_size(void); +static size_t get_env_user_used_size(void); +static EfErrCode create_env(const char *key, const char *value); +static uint32_t calc_env_crc(void); +static bool env_crc_is_ok(void); +#ifdef EF_ENV_AUTO_UPDATE +static EfErrCode env_auto_update(void); +#endif + +/** + * Flash ENV initialize. + * + * @param default_env default ENV set for user + * @param default_env_size default ENV set size + * + * @note user_size must equal with total_size in normal mode + * + * @return result + */ +EfErrCode ef_env_init(ef_env const *default_env, size_t default_env_size) { + EfErrCode result = EF_NO_ERR; + + EF_ASSERT(ENV_AREA_SIZE); + EF_ASSERT(ENV_USER_SETTING_SIZE); + EF_ASSERT(EF_ERASE_MIN_SIZE); + /* must be word alignment for ENV */ + EF_ASSERT(ENV_USER_SETTING_SIZE % 4 == 0); + EF_ASSERT(ENV_AREA_SIZE % 4 == 0); + EF_ASSERT(default_env); + EF_ASSERT(default_env_size < ENV_USER_SETTING_SIZE); + +#ifndef EF_ENV_USING_PFS_MODE + /* total_size must be aligned with erase_min_size */ + if (ENV_USER_SETTING_SIZE % EF_ERASE_MIN_SIZE == 0) { + EF_ASSERT(ENV_USER_SETTING_SIZE == ENV_AREA_SIZE); + } else { + EF_ASSERT((ENV_USER_SETTING_SIZE / EF_ERASE_MIN_SIZE + 1)*EF_ERASE_MIN_SIZE == ENV_AREA_SIZE); + } +#else + /* total_size must be aligned with erase_min_size */ + if (ENV_USER_SETTING_SIZE % EF_ERASE_MIN_SIZE == 0) { + /* it has double area when used power fail safeguard mode */ + EF_ASSERT(2 * ENV_USER_SETTING_SIZE == ENV_AREA_SIZE); + } else { + /* it has double area when used power fail safeguard mode */ + EF_ASSERT(2 * (ENV_USER_SETTING_SIZE / EF_ERASE_MIN_SIZE + 1)*EF_ERASE_MIN_SIZE == ENV_AREA_SIZE); + } +#endif + + env_start_addr = EF_START_ADDR; + default_env_set = default_env; + default_env_set_size = default_env_size; + + EF_DEBUG("ENV start address is 0x%08X, size is %d bytes.\n", EF_START_ADDR, ENV_AREA_SIZE); + + result = ef_load_env(); + +#ifdef EF_ENV_AUTO_UPDATE + if (result == EF_NO_ERR) { + env_auto_update(); + } +#endif + + if (result == EF_NO_ERR) { + init_ok = true; + } + + + return result; +} + +/** + * ENV set default. + * + * @return result + */ +EfErrCode ef_env_set_default(void) { + extern EfErrCode ef_env_ver_num_set_default(void); + + EfErrCode result = EF_NO_ERR; + size_t i; + + EF_ASSERT(default_env_set); + EF_ASSERT(default_env_set_size); + + /* lock the ENV cache */ + ef_port_env_lock(); + + /* set environment end address is at data section start address */ + set_env_end_addr(get_env_data_addr()); + +#ifdef EF_ENV_USING_PFS_MODE + /* set saved count to default 0 */ + env_cache[ENV_PARAM_INDEX_SAVED_COUNT] = 0; +#endif + +#ifdef EF_ENV_AUTO_UPDATE + /* initialize version number */ + env_cache[ENV_PARAM_INDEX_VER_NUM] = EF_ENV_VER_NUM; +#endif + + /* create default ENV */ + for (i = 0; i < default_env_set_size; i++) { + create_env(default_env_set[i].key, default_env_set[i].value); + } + + /* unlock the ENV cache */ + ef_port_env_unlock(); + + result = ef_save_env(); + +#ifdef EF_ENV_USING_PFS_MODE + /* reset other PFS area's data */ + if (result == EF_NO_ERR) { + env_cache_changed = true; + result = ef_save_env(); + } +#endif + + return result; +} + +/** + * Get ENV system section start address. + * + * @return system section start address + */ +static uint32_t get_env_system_addr(void) { +#ifndef EF_ENV_USING_PFS_MODE + return env_start_addr; +#else + return cur_load_area_addr; +#endif +} + +/** + * Get ENV data section start address. + * + * @return data section start address + */ +static uint32_t get_env_data_addr(void) { + return get_env_system_addr() + ENV_PARAM_BYTE_SIZE; +} + +/** + * Get ENV end address. + * It's the first word in ENV. + * + * @return ENV end address + */ +static uint32_t get_env_end_addr(void) { + /* it is the first word */ + return env_cache[ENV_PARAM_INDEX_END_ADDR]; +} + +/** + * Set ENV end address. + * It's the first word in ENV. + * + * @param end_addr ENV end address + */ +static void set_env_end_addr(uint32_t end_addr) { + env_cache[ENV_PARAM_INDEX_END_ADDR] = end_addr; +} + +/** + * Get current ENV data section size. + * + * @return size + */ +static size_t get_env_data_size(void) { + if (get_env_end_addr() > get_env_data_addr()) { + return get_env_end_addr() - get_env_data_addr(); + } else { + return 0; + } +} + +/** + * Get current user used ENV size. + * + * @return bytes + */ +static size_t get_env_user_used_size(void) { + if (get_env_end_addr() > get_env_system_addr()) { + return get_env_end_addr() - get_env_system_addr(); + } else { + return 0; + } +} + +/** + * Get current ENV already write bytes. + * + * @return write bytes + */ +size_t ef_get_env_write_bytes(void) { +#ifndef EF_ENV_USING_PFS_MODE + return get_env_user_used_size(); +#else + return get_env_user_used_size() * 2; +#endif +} + +/** + * Write an ENV at the end of cache. + * + * @param key ENV name + * @param value ENV value + * + * @return result + */ +static EfErrCode write_env(const char *key, const char *value) { + EfErrCode result = EF_NO_ERR; + size_t key_len = strlen(key), value_len = strlen(value), env_str_len; + char *env_cache_bak = (char *)env_cache; + + /* calculate ENV storage length, contain '=' and '\0'. */ + env_str_len = key_len + value_len + 2; + if (env_str_len % 4 != 0) { + env_str_len = (env_str_len / 4 + 1) * 4; + } + /* check capacity of ENV */ + if (env_str_len + get_env_user_used_size() >= ENV_USER_SETTING_SIZE) { + return EF_ENV_FULL; + } + + /* calculate current ENV ram cache end address */ + env_cache_bak += get_env_user_used_size(); + + /* copy key name */ + memcpy(env_cache_bak, key, key_len); + env_cache_bak += key_len; + /* copy equal sign */ + *env_cache_bak = '='; + env_cache_bak++; + /* copy value */ + memcpy(env_cache_bak, value, value_len); + env_cache_bak += value_len; + /* fill '\0' for string end sign */ + *env_cache_bak = '\0'; + env_cache_bak ++; + /* fill '\0' for word alignment */ + memset(env_cache_bak, 0, env_str_len - (key_len + value_len + 2)); + set_env_end_addr(get_env_end_addr() + env_str_len); + /* ENV ram cache has changed */ + env_cache_changed = true; + + return result; +} + +/** + * Find ENV. + * + * @param key ENV name + * + * @return found ENV in ram cache + */ +static char *find_env(const char *key) { + char *env_start, *env_end, *env, *found_env = NULL; + size_t key_len = strlen(key), env_len; + + if ((key == NULL) || *key == '\0') { + EF_INFO("Flash ENV name must be not empty!\n"); + return NULL; + } + + /* from data section start to data section end */ + env_start = (char *) ((char *) env_cache + ENV_PARAM_BYTE_SIZE); + env_end = (char *) ((char *) env_cache + get_env_user_used_size()); + + /* ENV is null */ + if (env_start == env_end) { + return NULL; + } + + env = env_start; + while (env < env_end) { + /* the key length must be equal */ + if (!strncmp(env, key, key_len) && (env[key_len] == '=')) { + found_env = env; + break; + } else { + /* calculate ENV length, contain '\0'. */ + env_len = strlen(env) + 1; + /* next ENV and word alignment */ + if (env_len % 4 == 0) { + env += env_len; + } else { + env += (env_len / 4 + 1) * 4; + } + } + } + return found_env; +} + +/** + * If the ENV is not exist, create it. + * @see flash_write_env + * + * @param key ENV name + * @param value ENV value + * + * @return result + */ +static EfErrCode create_env(const char *key, const char *value) { + EfErrCode result = EF_NO_ERR; + + EF_ASSERT(key); + EF_ASSERT(value); + + if ((key == NULL) || *key == '\0') { + EF_INFO("Flash ENV name must be not empty!\n"); + return EF_ENV_NAME_ERR; + } + + if (strchr(key, '=')) { + EF_INFO("Flash ENV name can't contain '='.\n"); + return EF_ENV_NAME_ERR; + } + + /* find ENV */ + if (find_env(key)) { + EF_INFO("The name of \"%s\" is already exist.\n", key); + return EF_ENV_NAME_EXIST; + } + /* write ENV at the end of cache */ + result = write_env(key, value); + + return result; +} + +/** + * Delete an ENV in cache. + * + * @param key ENV name + * + * @return result + */ +static EfErrCode del_env(const char *key) { + EfErrCode result = EF_NO_ERR; + char *del_env = NULL; + size_t del_env_length, remain_env_length; + + EF_ASSERT(key); + + if ((key == NULL) || *key == '\0') { + EF_INFO("Flash ENV name must be not NULL!\n"); + return EF_ENV_NAME_ERR; + } + + if (strchr(key, '=')) { + EF_INFO("Flash ENV name or value can't contain '='.\n"); + return EF_ENV_NAME_ERR; + } + + /* find ENV */ + del_env = find_env(key); + + if (!del_env) { + EF_INFO("Not find \"%s\" in ENV.\n", key); + return EF_ENV_NAME_ERR; + } + del_env_length = strlen(del_env); + /* '\0' also must be as ENV length */ + del_env_length ++; + /* the address must multiple of 4 */ + if (del_env_length % 4 != 0) { + del_env_length = (del_env_length / 4 + 1) * 4; + } + /* calculate remain ENV length */ + remain_env_length = get_env_data_size() + - (((uint32_t) del_env + del_env_length) - ((uint32_t) env_cache + ENV_PARAM_BYTE_SIZE)); + /* remain ENV move forward */ + memcpy(del_env, del_env + del_env_length, remain_env_length); + /* reset ENV end address */ + set_env_end_addr(get_env_end_addr() - del_env_length); + /* ENV ram cache has changed */ + env_cache_changed = true; + + return result; +} + +/** + * Set an ENV.If it value is NULL, delete it. + * If not find it in ENV table, then create it. + * + * @param key ENV name + * @param value ENV value + * + * @return result + */ +EfErrCode ef_set_env(const char *key, const char *value) { + EfErrCode result = EF_NO_ERR; + char *old_env, *old_value; + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return EF_ENV_INIT_FAILED; + } + + /* lock the ENV cache */ + ef_port_env_lock(); + + /* if ENV value is NULL, delete it */ + if (value == NULL) { + result = del_env(key); + } else { + old_env = find_env(key); + /* If find this ENV, then compare the new value and old value. */ + if (old_env) { + /* find the old value address */ + old_env = strchr(old_env, '='); + old_value = old_env + 1; + /* If it is changed then delete it and recreate it */ + if (strcmp(old_value, value)) { + result = del_env(key); + if (result == EF_NO_ERR) { + result = create_env(key, value); + } + } + } else { + result = create_env(key, value); + } + } + + /* unlock the ENV cache */ + ef_port_env_unlock(); + + return result; +} + +/** + * Del an ENV. + * + * @param key ENV name + * + * @return result + */ +EfErrCode ef_del_env(const char *key) { + EfErrCode result = EF_NO_ERR; + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return EF_ENV_INIT_FAILED; + } + + /* lock the ENV cache */ + ef_port_env_lock(); + + result = del_env(key); + + /* unlock the ENV cache */ + ef_port_env_unlock(); + + return result; +} + +/** + * Get an ENV value by key name. + * + * @param key ENV name + * + * @return value + */ +char *ef_get_env(const char *key) { + char *env = NULL, *value = NULL; + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return NULL; + } + + /* find ENV */ + env = find_env(key); + + if (env == NULL) { + return NULL; + } + /* get value address */ + value = strchr(env, '='); + if (value != NULL) { + /* the equal sign next character is value */ + value++; + } + return value; +} +/** + * Print ENV. + */ +void ef_print_env(void) { + uint32_t *env_cache_data_addr = env_cache + ENV_PARAM_WORD_SIZE, + *env_cache_end_addr = + (uint32_t *) (env_cache + ENV_PARAM_WORD_SIZE + get_env_data_size() / 4); + uint8_t j; + char c; + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return; + } + + for (; env_cache_data_addr < env_cache_end_addr; env_cache_data_addr += 1) { + for (j = 0; j < 4; j++) { + c = (*env_cache_data_addr) >> (8 * j); + ef_print("%c", c); + if (c == '\0') { + ef_print("\n"); + break; + } + } + } + +#ifndef EF_ENV_USING_PFS_MODE + ef_print("\nmode: normal\n"); + ef_print("size: %ld/%ld bytes.\n", get_env_user_used_size(), ENV_USER_SETTING_SIZE); +#else + ef_print("\nmode: power fail safeguard\n"); + ef_print("size: %ld/%ld bytes, write bytes %ld/%ld.\n", get_env_user_used_size(), + ENV_USER_SETTING_SIZE, ef_get_env_write_bytes(), ENV_AREA_SIZE); + ef_print("saved count: %ld\n", env_cache[ENV_PARAM_INDEX_SAVED_COUNT]); +#endif + +#ifdef EF_ENV_AUTO_UPDATE + ef_print("ver num: %d\n", env_cache[ENV_PARAM_INDEX_VER_NUM]); +#endif +} + +/** + * Load flash ENV to ram. + * + * @return result + */ +#ifndef EF_ENV_USING_PFS_MODE +EfErrCode ef_load_env(void) { + EfErrCode result = EF_NO_ERR; + uint32_t *env_cache_bak, env_end_addr; + + /* read ENV end address from flash */ + ef_port_read(get_env_system_addr() + ENV_PARAM_INDEX_END_ADDR * 4, &env_end_addr, 4); + /* if ENV is not initialize or flash has dirty data, set default for it */ + if ((env_end_addr == 0xFFFFFFFF) || (env_end_addr < env_start_addr) + || (env_end_addr > env_start_addr + ENV_USER_SETTING_SIZE)) { + result = ef_env_set_default(); + } else { + /* set ENV end address */ + set_env_end_addr(env_end_addr); + + env_cache_bak = env_cache + ENV_PARAM_WORD_SIZE; + /* read all ENV from flash */ + ef_port_read(get_env_data_addr(), env_cache_bak, get_env_data_size()); + /* read ENV CRC code from flash */ + ef_port_read(get_env_system_addr() + ENV_PARAM_INDEX_DATA_CRC * 4, + &env_cache[ENV_PARAM_INDEX_DATA_CRC] , 4); + /* if ENV CRC32 check is fault, set default for it */ + if (!env_crc_is_ok()) { + EF_INFO("Warning: ENV CRC check failed. Set it to default.\n"); + result = ef_env_set_default(); + } + } + return result; +} +#else +EfErrCode ef_load_env(void) { + EfErrCode result = EF_NO_ERR; + uint32_t area0_start_address = env_start_addr, area1_start_address = env_start_addr + + ENV_AREA_SIZE / 2; + uint32_t area0_end_addr, area1_end_addr, area0_crc, area1_crc, area0_saved_count, area1_saved_count; + bool area0_is_valid = true, area1_is_valid = true; + /* read ENV area end address from flash */ + ef_port_read(area0_start_address + ENV_PARAM_INDEX_END_ADDR * 4, &area0_end_addr, 4); + ef_port_read(area1_start_address + ENV_PARAM_INDEX_END_ADDR * 4, &area1_end_addr, 4); + if ((area0_end_addr == 0xFFFFFFFF) || (area0_end_addr < area0_start_address) + || (area0_end_addr > area0_start_address + ENV_USER_SETTING_SIZE)) { + area0_is_valid = false; + } + if ((area1_end_addr == 0xFFFFFFFF) || (area1_end_addr < area1_start_address) + || (area1_end_addr > area1_start_address + ENV_USER_SETTING_SIZE)) { + area1_is_valid = false; + } + /* check area0 CRC when it is valid */ + if (area0_is_valid) { + /* read ENV area0 crc32 code from flash */ + ef_port_read(area0_start_address + ENV_PARAM_INDEX_DATA_CRC * 4, &area0_crc, 4); + /* read ENV from ENV area0 */ + ef_port_read(area0_start_address, env_cache, area0_end_addr - area0_start_address); + /* current load ENV area address is area0 start address */ + cur_load_area_addr = area0_start_address; + if (!env_crc_is_ok()) { + area0_is_valid = false; + } + } + /* check area1 CRC when it is valid */ + if (area1_is_valid) { + /* read ENV area1 crc32 code from flash */ + ef_port_read(area1_start_address + ENV_PARAM_INDEX_DATA_CRC * 4, &area1_crc, 4); + /* read ENV from ENV area1 */ + ef_port_read(area1_start_address, env_cache, area1_end_addr - area1_start_address); + /* current load ENV area address is area1 start address */ + cur_load_area_addr = area1_start_address; + if (!env_crc_is_ok()) { + area1_is_valid = false; + } + } + /* all ENV area CRC is OK then compare saved count */ + if (area0_is_valid && area1_is_valid) { + /* read ENV area saved count from flash */ + ef_port_read(area0_start_address + ENV_PARAM_INDEX_SAVED_COUNT * 4, + &area0_saved_count, 4); + ef_port_read(area1_start_address + ENV_PARAM_INDEX_SAVED_COUNT * 4, + &area1_saved_count, 4); + /* the bigger saved count area is valid */ + if ((area0_saved_count > area1_saved_count) || ((area0_saved_count == 0) && (area1_saved_count == 0xFFFFFFFF))) { + area1_is_valid = false; + } else { + area0_is_valid = false; + } + } + if (area0_is_valid) { + /* current load ENV area address is area0 start address */ + cur_load_area_addr = area0_start_address; + /* next save ENV area address is area1 start address */ + next_save_area_addr = area1_start_address; + /* read all ENV from area0 */ + ef_port_read(area0_start_address, env_cache, area0_end_addr - area0_start_address); + } else if (area1_is_valid) { + /* next save ENV area address is area0 start address */ + next_save_area_addr = area0_start_address; + } else { + /* current load ENV area address is area1 start address */ + cur_load_area_addr = area1_start_address; + /* next save ENV area address is area0 start address */ + next_save_area_addr = area0_start_address; + /* set the ENV to default */ + result = ef_env_set_default(); + } + return result; +} +#endif + +/** + * Save ENV to flash. + */ +EfErrCode ef_save_env(void) { + EfErrCode result = EF_NO_ERR; + uint32_t write_addr, write_size; + + /* ENV ram cache has not changed don't need to save */ + if (!env_cache_changed) { + return result; + } + +#ifndef EF_ENV_USING_PFS_MODE + write_addr = get_env_system_addr(); + write_size = get_env_user_used_size(); + /* calculate and cache CRC32 code */ + env_cache[ENV_PARAM_INDEX_DATA_CRC] = calc_env_crc(); +#else + write_addr = next_save_area_addr; + write_size = get_env_user_used_size(); + /* replace next_save_area_addr with cur_load_area_addr */ + next_save_area_addr = cur_load_area_addr; + cur_load_area_addr = write_addr; + /* change the ENV end address to next save area address */ + set_env_end_addr(write_addr + write_size); + /* ENV area saved count +1 */ + env_cache[ENV_PARAM_INDEX_SAVED_COUNT]++; + /* calculate and cache CRC32 code */ + env_cache[ENV_PARAM_INDEX_DATA_CRC] = calc_env_crc(); +#endif + + /* erase ENV */ + result = ef_port_erase(write_addr, write_size); + switch (result) { + case EF_NO_ERR: { + EF_DEBUG("Erased ENV OK.\n"); + break; + } + case EF_ERASE_ERR: { + EF_INFO("Error: Erased ENV fault! Start address is 0x%08X, size is %ld.\n", write_addr, write_size); + /* will return when erase fault */ + return result; + } + } + + /* write ENV to flash */ + result = ef_port_write(write_addr, env_cache, write_size); + switch (result) { + case EF_NO_ERR: { + EF_DEBUG("Saved ENV OK.\n"); + break; + } + case EF_WRITE_ERR: { + EF_INFO("Error: Saved ENV fault! Start address is 0x%08X, size is %ld.\n", write_addr, write_size); + break; + } + } + + env_cache_changed = false; + + return result; +} + +/** + * Calculate the cached ENV CRC32 value. + * + * @return CRC32 value + */ +static uint32_t calc_env_crc(void) { + uint32_t crc32 = 0; + + /* Calculate the ENV end address CRC32. The 4 is ENV end address bytes size. */ + crc32 = ef_calc_crc32(crc32, &env_cache[ENV_PARAM_INDEX_END_ADDR], 4); + +#ifdef EF_ENV_USING_PFS_MODE + /* Calculate the ENV area saved count CRC32. */ + crc32 = ef_calc_crc32(crc32, &env_cache[ENV_PARAM_INDEX_SAVED_COUNT], 4); +#endif + + /* Calculate the all ENV data CRC32. */ + crc32 = ef_calc_crc32(crc32, &env_cache[ENV_PARAM_WORD_SIZE], get_env_data_size()); + + EF_DEBUG("Calculate ENV CRC32 number is 0x%08X.\n", crc32); + + return crc32; +} + +/** + * Check the ENV CRC32 + * + * @return true is ok + */ +static bool env_crc_is_ok(void) { + if (calc_env_crc() == env_cache[ENV_PARAM_INDEX_DATA_CRC]) { + EF_DEBUG("Verify ENV CRC32 result is OK.\n"); + return true; + } else { + return false; + } +} + +/** + * Set and save an ENV. If set ENV is success then will save it. + * + * @param key ENV name + * @param value ENV value + * + * @return result + */ +EfErrCode ef_set_and_save_env(const char *key, const char *value) { + EfErrCode result = EF_NO_ERR; + + result = ef_set_env(key, value); + + if (result == EF_NO_ERR) { + result = ef_save_env(); + } + + return result; +} + +/** + * Del and save an ENV. If del ENV is success then will save it. + * + * @param key ENV name + * + * @return result + */ +EfErrCode ef_del_and_save_env(const char *key) { + EfErrCode result = EF_NO_ERR; + + result = ef_del_env(key); + + if (result == EF_NO_ERR) { + result = ef_save_env(); + } + + return result; +} + +#ifdef EF_ENV_AUTO_UPDATE +/** + * Auto update ENV to latest default when current EF_ENV_VER is changed. + * + * @return result + */ +static EfErrCode env_auto_update(void) +{ + size_t i; + + /* lock the ENV cache */ + ef_port_env_lock(); + + /* read ENV version number from flash*/ + ef_port_read(get_env_system_addr() + ENV_PARAM_INDEX_VER_NUM * 4, + &env_cache[ENV_PARAM_INDEX_VER_NUM] , 4); + + /* check version number */ + if (env_cache[ENV_PARAM_INDEX_VER_NUM] != EF_ENV_VER_NUM) { + env_cache_changed = true; + /* update version number */ + env_cache[ENV_PARAM_INDEX_VER_NUM] = EF_ENV_VER_NUM; + /* add a new ENV when it's not found */ + for (i = 0; i < default_env_set_size; i++) { + if (find_env(default_env_set[i].key) == NULL) { + create_env(default_env_set[i].key, default_env_set[i].value); + } + } + } + + /* unlock the ENV cache */ + ef_port_env_unlock(); + + return ef_save_env(); +} +#endif /* EF_ENV_AUTO_UPDATE */ + +#endif /* EF_ENV_USING_WL_MODE */ + +#endif /* defined(EF_USING_ENV) && defined(EF_ENV_USING_LEGACY_MODE) */ diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_env_legacy_wl.c b/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_env_legacy_wl.c new file mode 100644 index 0000000..1630890 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_env_legacy_wl.c @@ -0,0 +1,1082 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2015-2018, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: Environment variables operating interface. (wear leveling mode) + * Created on: 2015-02-11 + */ + +#include +#include +#include + +#if defined(EF_USING_ENV) && defined(EF_ENV_USING_LEGACY_MODE) + +#ifdef EF_ENV_USING_WL_MODE + +#if defined(EF_USING_ENV) && (!defined(ENV_USER_SETTING_SIZE) || !defined(ENV_AREA_SIZE)) +#error "Please configure user setting ENV size or ENV area size (in ef_cfg.h)" +#endif + +/** + * ENV area has 2 sections + * 1. System section + * Storage ENV current using data section address. + * Units: Word. Total size: @see EF_ERASE_MIN_SIZE. + * 2. Data section + * The data section storage ENV's parameters and detail. + * When an exception has occurred on flash erase or write. The current using data section + * address will move to next available position. This position depends on EF_ERASE_MIN_SIZE. + * 2.1 ENV parameters part + * It storage ENV's parameters. + * 2.2 ENV detail part + * It storage all ENV. Storage format is key=value\0. + * All ENV must be 4 bytes alignment. The remaining part must fill '\0'. + * + * @note Word = 4 Bytes in this file + * @note It will has two ENV areas(Area0, Area1) in data section when used power fail safeguard mode. + */ + +/* flash ENV parameters part index and size */ +enum { + /* data section ENV detail part end address index */ + ENV_PARAM_PART_INDEX_END_ADDR = 0, + +#ifdef EF_ENV_USING_PFS_MODE + /* saved count for ENV area */ + ENV_PARAM_PART_INDEX_SAVED_COUNT, +#endif + +#ifdef EF_ENV_AUTO_UPDATE + /* current version number for ENV */ + ENV_PARAM_INDEX_VER_NUM, +#endif + + /* data section CRC32 code index */ + ENV_PARAM_PART_INDEX_DATA_CRC, + /* ENV parameters part word size */ + ENV_PARAM_PART_WORD_SIZE, + /* ENV parameters part byte size */ + ENV_PARAM_PART_BYTE_SIZE = ENV_PARAM_PART_WORD_SIZE * 4, +}; + +/* default ENV set, must be initialized by user */ +static ef_env const *default_env_set; +/* default ENV set size, must be initialized by user */ +static size_t default_env_set_size = 0; +/* flash ENV data section size */ +static size_t env_data_section_size = 0; +/* ENV ram cache */ +static uint32_t env_cache[ENV_USER_SETTING_SIZE / 4] = { 0 }; +/* ENV start address in flash */ +static uint32_t env_start_addr = 0; +/* current using data section address */ +static uint32_t cur_using_data_addr = 0; +/* ENV ram cache has changed when ENV created, deleted and changed value. */ +static bool env_cache_changed = false; +/* initialize OK flag */ +static bool init_ok = false; + +#ifdef EF_ENV_USING_PFS_MODE +/* next save ENV area address */ +static uint32_t next_save_area_addr = 0; +#endif + +static uint32_t get_env_start_addr(void); +static uint32_t get_cur_using_data_addr(void); +static uint32_t get_env_detail_addr(void); +static uint32_t get_env_detail_end_addr(void); +static void set_cur_using_data_addr(uint32_t using_data_addr); +static void set_env_detail_end_addr(uint32_t end_addr); +static EfErrCode write_env(const char *key, const char *value); +static char *find_env(const char *key); +static size_t get_env_detail_size(void); +static size_t get_env_user_used_size(void); +static EfErrCode create_env(const char *key, const char *value); +static EfErrCode del_env(const char *key); +static EfErrCode save_cur_using_data_addr(uint32_t cur_data_addr); +static uint32_t calc_env_crc(void); +static bool env_crc_is_ok(void); +#ifdef EF_ENV_AUTO_UPDATE +static EfErrCode env_auto_update(void); +#endif + +/** + * Flash ENV initialize. + * + * @param default_env default ENV set for user + * @param default_env_size default ENV set size + * + * @return result + */ +EfErrCode ef_env_init(ef_env const *default_env, size_t default_env_size) { + EfErrCode result = EF_NO_ERR; + + EF_ASSERT(ENV_AREA_SIZE); + EF_ASSERT(ENV_USER_SETTING_SIZE); + /* must be word alignment for ENV */ + EF_ASSERT(ENV_USER_SETTING_SIZE % 4 == 0); + EF_ASSERT(ENV_AREA_SIZE % 4 == 0); + EF_ASSERT(default_env); + EF_ASSERT(default_env_size < ENV_USER_SETTING_SIZE); + +#ifndef EF_ENV_USING_PFS_MODE + /* system section size is erase_min_size, so last part is data section */ + env_data_section_size = ENV_AREA_SIZE - EF_ERASE_MIN_SIZE; +#else + /* system section size is erase_min_size, so last part is data section */ + env_data_section_size = ENV_AREA_SIZE / 2 - EF_ERASE_MIN_SIZE; + EF_ASSERT((ENV_AREA_SIZE / EF_ERASE_MIN_SIZE) % 2 == 0); +#endif + EF_ASSERT(env_data_section_size >= ENV_USER_SETTING_SIZE); + /* the ENV data section size should be an integral multiple of erase minimum size. */ + EF_ASSERT(env_data_section_size % EF_ERASE_MIN_SIZE == 0); + + + env_start_addr = EF_START_ADDR; + default_env_set = default_env; + default_env_set_size = default_env_size; + + EF_DEBUG("ENV start address is 0x%08X, size is %d bytes.\n", EF_START_ADDR, ENV_AREA_SIZE); + + result = ef_load_env(); + +#ifdef EF_ENV_AUTO_UPDATE + if (result == EF_NO_ERR) { + env_auto_update(); + } +#endif + + if (result == EF_NO_ERR) { + init_ok = true; + } + + return result; +} + +/** + * ENV set default. + * + * @return result + */ +EfErrCode ef_env_set_default(void) { + EfErrCode result = EF_NO_ERR; + size_t i; + + EF_ASSERT(default_env_set); + EF_ASSERT(default_env_set_size); + + /* lock the ENV cache */ + ef_port_env_lock(); + + /* set ENV detail part end address is at ENV detail part start address */ + set_env_detail_end_addr(get_env_detail_addr()); + +#ifdef EF_ENV_USING_PFS_MODE + /* set saved count to default 0 */ + env_cache[ENV_PARAM_PART_INDEX_SAVED_COUNT] = 0; +#endif + +#ifdef EF_ENV_AUTO_UPDATE + /* initialize version number */ + env_cache[ENV_PARAM_INDEX_VER_NUM] = EF_ENV_VER_NUM; +#endif + + /* create default ENV */ + for (i = 0; i < default_env_set_size; i++) { + create_env(default_env_set[i].key, default_env_set[i].value); + } + + /* unlock the ENV cache */ + ef_port_env_unlock(); + + result = ef_save_env(); + +#ifdef EF_ENV_USING_PFS_MODE + /* reset other PFS area's data */ + if (result == EF_NO_ERR) { + env_cache_changed = true; + result = ef_save_env(); + } +#endif + + return result; +} + +/** + * Get ENV start address in flash. + * + * @return ENV start address in flash + */ +static uint32_t get_env_start_addr(void) { + return env_start_addr; +} +/** + * Get current using data section address. + * + * @return current using data section address + */ +static uint32_t get_cur_using_data_addr(void) { + return cur_using_data_addr; +} + +/** + * Set current using data section address. + * + * @param using_data_addr current using data section address + */ +static void set_cur_using_data_addr(uint32_t using_data_addr) { + cur_using_data_addr = using_data_addr; +} + +/** + * Get ENV detail part start address. + * + * @return detail part start address + */ +static uint32_t get_env_detail_addr(void) { + return get_cur_using_data_addr() + ENV_PARAM_PART_BYTE_SIZE; +} + +/** + * Get ENV detail part end address. + * It's the first word in ENV. + * + * @return ENV end address + */ +static uint32_t get_env_detail_end_addr(void) { + /* it is the first word */ + return env_cache[ENV_PARAM_PART_INDEX_END_ADDR]; +} + +/** + * Set ENV detail part end address. + * It's the first word in ENV. + * + * @param end_addr ENV end address + */ +static void set_env_detail_end_addr(uint32_t end_addr) { + env_cache[ENV_PARAM_PART_INDEX_END_ADDR] = end_addr; +} + +/** + * Get current ENV detail part size. + * + * @return size + */ +static size_t get_env_detail_size(void) { + if (get_env_detail_end_addr() > get_env_detail_addr()) { + return get_env_detail_end_addr() - get_env_detail_addr(); + } else { + return 0; + } +} + +/** + * Get current user used ENV size. + * + * @see ENV_USER_SETTING_SIZE + * + * @return size + */ +/* must be initialized */ +static size_t get_env_user_used_size(void) { + if (get_env_detail_end_addr() > get_cur_using_data_addr()) { + return get_env_detail_end_addr() - get_cur_using_data_addr(); + } else { + return 0; + } +} + +/** + * Get current ENV already write bytes. + * + * @return write bytes + */ +size_t ef_get_env_write_bytes(void) { +#ifndef EF_ENV_USING_PFS_MODE + return get_env_detail_end_addr() - get_env_start_addr(); +#else + return EF_ERASE_MIN_SIZE + get_env_detail_end_addr() - get_cur_using_data_addr(); +#endif +} + +/** + * Write an ENV at the end of cache. + * + * @param key ENV name + * @param value ENV value + * + * @return result + */ +static EfErrCode write_env(const char *key, const char *value) { + EfErrCode result = EF_NO_ERR; + size_t ker_len = strlen(key), value_len = strlen(value), env_str_len; + char *env_cache_bak = (char *)env_cache; + + /* calculate ENV storage length, contain '=' and '\0'. */ + env_str_len = ker_len + value_len + 2; + if (env_str_len % 4 != 0) { + env_str_len = (env_str_len / 4 + 1) * 4; + } + /* check capacity of ENV */ + if (env_str_len + get_env_user_used_size() >= ENV_USER_SETTING_SIZE) { + return EF_ENV_FULL; + } + /* calculate current ENV ram cache end address */ + env_cache_bak += ENV_PARAM_PART_BYTE_SIZE + get_env_detail_size(); + /* copy key name */ + memcpy(env_cache_bak, key, ker_len); + env_cache_bak += ker_len; + /* copy equal sign */ + *env_cache_bak = '='; + env_cache_bak++; + /* copy value */ + memcpy(env_cache_bak, value, value_len); + env_cache_bak += value_len; + /* fill '\0' for string end sign */ + *env_cache_bak = '\0'; + env_cache_bak ++; + /* fill '\0' for word alignment */ + memset(env_cache_bak, 0, env_str_len - (ker_len + value_len + 2)); + set_env_detail_end_addr(get_env_detail_end_addr() + env_str_len); + /* ENV ram cache has changed */ + env_cache_changed = true; + + return result; +} + +/** + * Find ENV. + * + * @param key ENV name + * + * @return found ENV in ram cache + */ +static char *find_env(const char *key) { + char *env_start, *env_end, *env, *found_env = NULL; + size_t key_len = strlen(key), env_len; + + if ((key == NULL) || (*key == '\0')) { + EF_INFO("Flash ENV name must be not empty!\n"); + return NULL; + } + + /* from data section start to data section end */ + env_start = (char *) ((char *) env_cache + ENV_PARAM_PART_BYTE_SIZE); + env_end = (char *) ((char *) env_cache + ENV_PARAM_PART_BYTE_SIZE + get_env_detail_size()); + + /* ENV is null */ + if (env_start == env_end) { + return NULL; + } + + env = env_start; + while (env < env_end) { + /* the key length must be equal */ + if (!strncmp(env, key, key_len) && (env[key_len] == '=')) { + found_env = env; + break; + } else { + /* calculate ENV length, contain '\0'. */ + env_len = strlen(env) + 1; + /* next ENV and word alignment */ + if (env_len % 4 == 0) { + env += env_len; + } else { + env += (env_len / 4 + 1) * 4; + } + } + } + + return found_env; +} + +/** + * If the ENV is not exist, create it. + * @see flash_write_env + * + * @param key ENV name + * @param value ENV value + * + * @return result + */ +static EfErrCode create_env(const char *key, const char *value) { + EfErrCode result = EF_NO_ERR; + + EF_ASSERT(key); + EF_ASSERT(value); + + if ((key == NULL) || (*key == '\0')) { + EF_INFO("Flash ENV name must be not empty!\n"); + return EF_ENV_NAME_ERR; + } + + if (strchr(key, '=')) { + EF_INFO("Flash ENV name can't contain '='.\n"); + return EF_ENV_NAME_ERR; + } + + /* find ENV */ + if (find_env(key)) { + EF_INFO("The name of \"%s\" is already exist.\n", key); + return EF_ENV_NAME_EXIST; + } + /* write ENV at the end of cache */ + result = write_env(key, value); + + return result; +} + +/** + * Delete an ENV in cache. + * + * @param key ENV name + * + * @return result + */ +static EfErrCode del_env(const char *key) { + EfErrCode result = EF_NO_ERR; + char *del_env = NULL; + size_t del_env_length, remain_env_length; + + EF_ASSERT(key); + + if ((key == NULL) || (*key == '\0')) { + EF_INFO("Flash ENV name must be not NULL!\n"); + return EF_ENV_NAME_ERR; + } + + if (strchr(key, '=')) { + EF_INFO("Flash ENV name or value can't contain '='.\n"); + return EF_ENV_NAME_ERR; + } + + /* find ENV */ + del_env = find_env(key); + + if (!del_env) { + EF_INFO("Not find \"%s\" in ENV.\n", key); + return EF_ENV_NAME_ERR; + } + del_env_length = strlen(del_env); + /* '\0' also must be as ENV length */ + del_env_length ++; + /* the address must multiple of 4 */ + if (del_env_length % 4 != 0) { + del_env_length = (del_env_length / 4 + 1) * 4; + } + /* calculate remain ENV length */ + remain_env_length = get_env_detail_size() + - (((uint32_t) del_env + del_env_length) - ((uint32_t) env_cache + ENV_PARAM_PART_BYTE_SIZE)); + /* remain ENV move forward */ + memcpy(del_env, del_env + del_env_length, remain_env_length); + /* reset ENV end address */ + set_env_detail_end_addr(get_env_detail_end_addr() - del_env_length); + /* ENV ram cache has changed */ + env_cache_changed = true; + + return result; +} + +/** + * Set an ENV.If it value is NULL, delete it. + * If not find it in ENV table, then create it. + * + * @param key ENV name + * @param value ENV value + * + * @return result + */ +EfErrCode ef_set_env(const char *key, const char *value) { + EfErrCode result = EF_NO_ERR; + char *old_env, *old_value; + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return EF_ENV_INIT_FAILED; + } + + /* lock the ENV cache */ + ef_port_env_lock(); + + /* if ENV value is NULL, delete it */ + if (value == NULL) { + result = del_env(key); + } else { + old_env = find_env(key); + /* If find this ENV, then compare the new value and old value. */ + if (old_env) { + /* find the old value address */ + old_env = strchr(old_env, '='); + old_value = old_env + 1; + /* If it is changed then delete it and recreate it */ + if (strcmp(old_value, value)) { + result = del_env(key); + if (result == EF_NO_ERR) { + result = create_env(key, value); + } + } + } else { + result = create_env(key, value); + } + } + /* unlock the ENV cache */ + ef_port_env_unlock(); + + return result; +} + +/** + * Del an ENV. + * + * @param key ENV name + * + * @return result + */ +EfErrCode ef_del_env(const char *key) { + EfErrCode result = EF_NO_ERR; + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return EF_ENV_INIT_FAILED; + } + + /* lock the ENV cache */ + ef_port_env_lock(); + + result = del_env(key); + + /* unlock the ENV cache */ + ef_port_env_unlock(); + + return result; +} + +/** + * Get an ENV value by key name. + * + * @param key ENV name + * + * @return value + */ +char *ef_get_env(const char *key) { + char *env = NULL, *value = NULL; + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return NULL; + } + + /* find ENV */ + env = find_env(key); + + if (env == NULL) { + return NULL; + } + /* get value address */ + value = strchr(env, '='); + if (value != NULL) { + /* the equal sign next character is value */ + value++; + } + return value; +} +/** + * Print ENV. + */ +void ef_print_env(void) { + uint32_t *env_cache_detail_addr = env_cache + ENV_PARAM_PART_WORD_SIZE, *env_cache_end_addr = + (uint32_t *) (env_cache + ENV_PARAM_PART_WORD_SIZE + get_env_detail_size() / 4); + uint8_t j; + char c; + + if (!init_ok) { + EF_INFO("ENV isn't initialize OK.\n"); + return; + } + + for (; env_cache_detail_addr < env_cache_end_addr; env_cache_detail_addr += 1) { + for (j = 0; j < 4; j++) { + c = (*env_cache_detail_addr) >> (8 * j); + ef_print("%c", c); + if (c == '\0') { + ef_print("\n"); + break; + } + } + } + +#ifndef EF_ENV_USING_PFS_MODE + ef_print("\nmode: wear leveling\n"); + ef_print("size: %ld/%ld bytes, write bytes %ld/%ld.\n", get_env_user_used_size(), ENV_USER_SETTING_SIZE, + ef_get_env_write_bytes(), ENV_AREA_SIZE); +#else + ef_print("\nmode: wear leveling and power fail safeguard\n"); + ef_print("size: %ld/%ld bytes, write bytes %ld/%ld.\n", get_env_user_used_size(), ENV_USER_SETTING_SIZE, + ef_get_env_write_bytes(), ENV_AREA_SIZE / 2); + ef_print("saved count: %ld\n", env_cache[ENV_PARAM_PART_INDEX_SAVED_COUNT]); +#endif + +#ifdef EF_ENV_AUTO_UPDATE + ef_print("ver num: %d\n", env_cache[ENV_PARAM_INDEX_VER_NUM]); +#endif +} + +/** + * Load flash ENV to ram. + * + * @return result + */ +#ifndef EF_ENV_USING_PFS_MODE +EfErrCode ef_load_env(void) { + EfErrCode result = EF_NO_ERR; + uint32_t *env_cache_bak, env_end_addr, using_data_addr; + + /* read current using data section address */ + ef_port_read(get_env_start_addr(), &using_data_addr, 4); + /* if ENV is not initialize or flash has dirty data, set default for it */ + if ((using_data_addr == 0xFFFFFFFF) + || (using_data_addr > get_env_start_addr() + ENV_AREA_SIZE) + || (using_data_addr < get_env_start_addr() + EF_ERASE_MIN_SIZE)) { + /* initialize current using data section address */ + set_cur_using_data_addr(get_env_start_addr() + EF_ERASE_MIN_SIZE); + /* save current using data section address to flash*/ + if ((result = save_cur_using_data_addr(get_cur_using_data_addr())) == EF_NO_ERR) { + /* set default ENV */ + result = ef_env_set_default(); + } + } else { + /* set current using data section address */ + set_cur_using_data_addr(using_data_addr); + /* read ENV detail part end address from flash */ + ef_port_read(get_cur_using_data_addr() + ENV_PARAM_PART_INDEX_END_ADDR * 4, &env_end_addr, 4); + /* if ENV end address has error, set default for ENV */ + if (env_end_addr > get_env_start_addr() + ENV_AREA_SIZE) { + /* initialize current using data section address */ + set_cur_using_data_addr(get_env_start_addr() + EF_ERASE_MIN_SIZE); + /* save current using data section address to flash*/ + if ((result = save_cur_using_data_addr(get_cur_using_data_addr())) == EF_NO_ERR) { + EF_INFO("Warning: ENV end address has error. Set it to default.\n"); + result = ef_env_set_default(); + } + } else { + /* set ENV detail part end address */ + set_env_detail_end_addr(env_end_addr); + + env_cache_bak = env_cache + ENV_PARAM_PART_WORD_SIZE; + /* read all ENV from flash */ + ef_port_read(get_env_detail_addr(), env_cache_bak, get_env_detail_size()); + /* read ENV CRC code from flash */ + ef_port_read(get_cur_using_data_addr() + ENV_PARAM_PART_INDEX_DATA_CRC * 4, + &env_cache[ENV_PARAM_PART_INDEX_DATA_CRC], 4); + /* if ENV CRC32 check is fault, set default for it */ + if (!env_crc_is_ok()) { + EF_INFO("Warning: ENV CRC check failed. Set it to default.\n"); + result = ef_env_set_default(); + } + } + + } + return result; +} +#else +EfErrCode ef_load_env(void) { + EfErrCode result = EF_NO_ERR; + /* ENV area0 current using address default value */ + uint32_t area0_default_cur_using_addr = get_env_start_addr() + EF_ERASE_MIN_SIZE; + /* ENV area1 current using address default value */ + uint32_t area1_default_cur_using_addr = area0_default_cur_using_addr + ENV_AREA_SIZE / 2; + uint32_t area0_cur_using_addr, area1_cur_using_addr, area0_end_addr, area1_end_addr; + uint32_t area0_crc, area1_crc, area0_saved_count, area1_saved_count; + bool area0_is_valid = true, area1_is_valid = true; + + /* read ENV area0 and area1 current using data section address */ + ef_port_read(get_env_start_addr(), &area0_cur_using_addr, 4); + ef_port_read(get_env_start_addr() + ENV_AREA_SIZE / 2, &area1_cur_using_addr, 4); + /* if ENV is not initialize or flash has dirty data, set it isn't valid */ + if ((area0_cur_using_addr == 0xFFFFFFFF) + || (area0_cur_using_addr > get_env_start_addr() + ENV_AREA_SIZE / 2) + || (area0_cur_using_addr < get_env_start_addr() + EF_ERASE_MIN_SIZE)) { + area0_is_valid = false; + } + if ((area1_cur_using_addr == 0xFFFFFFFF) + || (area1_cur_using_addr > get_env_start_addr() + ENV_AREA_SIZE) + || (area1_cur_using_addr < get_env_start_addr() + ENV_AREA_SIZE / 2 + EF_ERASE_MIN_SIZE)) { + area1_is_valid = false; + } + /* check area0 end address when it is valid */ + if (area0_is_valid) { + /* read ENV area end address from flash */ + ef_port_read(area0_cur_using_addr + ENV_PARAM_PART_INDEX_END_ADDR * 4, &area0_end_addr, 4); + if ((area0_end_addr == 0xFFFFFFFF) || (area0_end_addr < area0_cur_using_addr) + || (area0_end_addr > area0_cur_using_addr + ENV_USER_SETTING_SIZE)) { + area0_is_valid = false; + } + } + /* check area1 end address when it is valid */ + if (area1_is_valid) { + /* read ENV area end address from flash */ + ef_port_read(area1_cur_using_addr + ENV_PARAM_PART_INDEX_END_ADDR * 4, &area1_end_addr, 4); + if ((area1_end_addr == 0xFFFFFFFF) || (area1_end_addr < area1_cur_using_addr) + || (area1_end_addr > area1_cur_using_addr + ENV_USER_SETTING_SIZE)) { + area1_is_valid = false; + } + } + /* check area0 CRC when it is valid */ + if (area0_is_valid) { + /* read ENV area0 crc32 code from flash */ + ef_port_read(area0_cur_using_addr + ENV_PARAM_PART_INDEX_DATA_CRC * 4, &area0_crc, 4); + /* read ENV from ENV area0 */ + ef_port_read(area0_cur_using_addr, env_cache, area0_end_addr - area0_cur_using_addr); + /* current using data section address is area0 current using data section address */ + set_cur_using_data_addr(area0_cur_using_addr); + if (!env_crc_is_ok()) { + area0_is_valid = false; + } + } + /* check area1 CRC when it is valid */ + if (area1_is_valid) { + /* read ENV area1 crc32 code from flash */ + ef_port_read(area1_cur_using_addr + ENV_PARAM_PART_INDEX_DATA_CRC * 4, &area1_crc, 4); + /* read ENV from ENV area1 */ + ef_port_read(area1_cur_using_addr, env_cache, area1_end_addr - area1_cur_using_addr); + /* current using data section address is area1 current using data section address */ + set_cur_using_data_addr(area1_cur_using_addr); + if (!env_crc_is_ok()) { + area1_is_valid = false; + } + } + /* all ENV area CRC is OK then compare saved count */ + if (area0_is_valid && area1_is_valid) { + /* read ENV area saved count from flash */ + ef_port_read(area0_cur_using_addr + ENV_PARAM_PART_INDEX_SAVED_COUNT * 4, + &area0_saved_count, 4); + ef_port_read(area1_cur_using_addr + ENV_PARAM_PART_INDEX_SAVED_COUNT * 4, + &area1_saved_count, 4); + /* the bigger saved count area is valid */ + if ((area0_saved_count > area1_saved_count) || ((area0_saved_count == 0) && (area1_saved_count == 0xFFFFFFFF))) { + area1_is_valid = false; + } else { + area0_is_valid = false; + } + } + if (area0_is_valid) { + /* current using data section address is area0 current using data section address */ + set_cur_using_data_addr(area0_cur_using_addr); + /* next save ENV area address is area1 current using address value */ + next_save_area_addr = area1_cur_using_addr; + /* read all ENV from area0 */ + ef_port_read(area0_cur_using_addr, env_cache, area0_end_addr - area0_cur_using_addr); + } else if (area1_is_valid) { + /* already read data section and set_cur_using_data_addr above current code, + * so just set next save ENV area address is area0 current using address value */ + next_save_area_addr = area0_cur_using_addr; + } else { + /* current using data section address is area1 current using address default value */ + set_cur_using_data_addr(area1_default_cur_using_addr); + /* next save ENV area address default is area0 current using address default value */ + next_save_area_addr = area0_default_cur_using_addr; + /* save current using data section address to flash*/ + if (((result = save_cur_using_data_addr(area0_default_cur_using_addr)) == EF_NO_ERR) + && ((result = save_cur_using_data_addr(area1_default_cur_using_addr)) == EF_NO_ERR)) { + /* set the ENV to default */ + result = ef_env_set_default(); + } + } + return result; +} +#endif + +/** + * Save ENV to flash. + */ +EfErrCode ef_save_env(void) { + EfErrCode result = EF_NO_ERR; + uint32_t cur_using_addr_bak, move_offset_addr; + size_t env_used_size = get_env_user_used_size(); + uint32_t data_sec_end_addr; + + /* ENV ram cache has not changed don't need to save */ + if (!env_cache_changed) { + return result; + } + +#ifndef EF_ENV_USING_PFS_MODE + data_sec_end_addr = get_env_start_addr() + ENV_AREA_SIZE - 4; + cur_using_addr_bak = get_cur_using_data_addr(); +#else + cur_using_addr_bak = next_save_area_addr; + /* replace next_save_area_addr with cur_using_data_addr */ + next_save_area_addr = get_cur_using_data_addr(); + set_cur_using_data_addr(cur_using_addr_bak); + /* change the ENV detail end address to next save area address */ + set_env_detail_end_addr(get_cur_using_data_addr() + env_used_size); + /* area0 or area1 */ + if (get_cur_using_data_addr() < get_env_start_addr() + ENV_AREA_SIZE / 2) { + data_sec_end_addr = get_env_start_addr() + ENV_AREA_SIZE / 2 - 4; + } else { + data_sec_end_addr = get_env_start_addr() + ENV_AREA_SIZE - 4; + } + /* ENV area saved count +1 */ + env_cache[ENV_PARAM_PART_INDEX_SAVED_COUNT]++; +#endif + + /* wear leveling process, automatic move ENV to next available position */ + while (get_cur_using_data_addr() + env_used_size < data_sec_end_addr) { + /* calculate and cache CRC32 code */ + env_cache[ENV_PARAM_PART_INDEX_DATA_CRC] = calc_env_crc(); + /* erase ENV */ + result = ef_port_erase(get_cur_using_data_addr(), env_used_size); + switch (result) { + case EF_NO_ERR: { + EF_DEBUG("Erased ENV OK.\n"); + break; + } + case EF_ERASE_ERR: { + EF_INFO("Warning: Erased ENV fault! Start address is 0x%08X, size is %ld.\n", + get_cur_using_data_addr(), env_used_size); + EF_INFO("Moving ENV to next available position.\n"); + /* Calculate move offset address. + * Current strategy is optimistic. It will offset the flash erasure minimum size. + */ + move_offset_addr = EF_ERASE_MIN_SIZE; + /* calculate and set next available data section address */ + set_cur_using_data_addr(get_cur_using_data_addr() + move_offset_addr); + /* calculate and set next available ENV detail part end address */ + set_env_detail_end_addr(get_env_detail_end_addr() + move_offset_addr); + continue; + } + } + /* write ENV to flash */ + result = ef_port_write(get_cur_using_data_addr(), env_cache, env_used_size); + switch (result) { + case EF_NO_ERR: { + EF_DEBUG("Saved ENV OK.\n"); + break; + } + case EF_WRITE_ERR: { + EF_INFO("Warning: Saved ENV fault! Start address is 0x%08X, size is %ld.\n", + get_cur_using_data_addr(), env_used_size); + EF_INFO("Moving ENV to next available position.\n"); + /* Calculate move offset address. + * Current strategy is optimistic. It will offset the flash erasure minimum size. + */ + move_offset_addr = EF_ERASE_MIN_SIZE; + /* calculate and set next available data section address */ + set_cur_using_data_addr(get_cur_using_data_addr() + move_offset_addr); + /* calculate and set next available ENV detail part end address */ + set_env_detail_end_addr(get_env_detail_end_addr() + move_offset_addr); + continue; + } + } + /* save ENV success */ + if (result == EF_NO_ERR) { + break; + } + } + + if (get_cur_using_data_addr() + env_used_size < data_sec_end_addr) { + /* current using data section address has changed, save it */ + if (get_cur_using_data_addr() != cur_using_addr_bak) { + result = save_cur_using_data_addr(get_cur_using_data_addr()); + } + } else { + result = EF_ENV_FULL; + EF_INFO("Error: The flash has no available space to save ENV.\n"); + } + + env_cache_changed = false; + + return result; +} + +/** + * Calculate the cached ENV CRC32 value. + * + * @return CRC32 value + */ +static uint32_t calc_env_crc(void) { + uint32_t crc32 = 0; + + /* Calculate the ENV end address and all ENV data CRC32. + * The 4 is ENV end address bytes size. */ + crc32 = ef_calc_crc32(crc32, &env_cache[ENV_PARAM_PART_INDEX_END_ADDR], 4); + crc32 = ef_calc_crc32(crc32, &env_cache[ENV_PARAM_PART_WORD_SIZE], get_env_detail_size()); + EF_DEBUG("Calculate ENV CRC32 number is 0x%08X.\n", crc32); + + return crc32; +} + +/** + * Check the ENV CRC32 + * + * @return true is ok + */ +static bool env_crc_is_ok(void) { + if (calc_env_crc() == env_cache[ENV_PARAM_PART_INDEX_DATA_CRC]) { + EF_DEBUG("Verify ENV CRC32 result is OK.\n"); + return true; + } else { + return false; + } +} + +/** + * Save current using data section address to flash. + * + * @param cur_data_addr current using data section address + * + * @return result + */ +#ifndef EF_ENV_USING_PFS_MODE +static EfErrCode save_cur_using_data_addr(uint32_t cur_data_addr) { + EfErrCode result = EF_NO_ERR; + + /* erase ENV system section */ + result = ef_port_erase(get_env_start_addr(), 4); + if (result == EF_NO_ERR) { + /* write current using data section address to flash */ + result = ef_port_write(get_env_start_addr(), &cur_data_addr, 4); + if (result == EF_WRITE_ERR) { + EF_INFO("Error: Write system section fault! Start address is 0x%08X, size is %ld.\n", + get_env_start_addr(), 4); + EF_INFO("Note: The ENV can not be used.\n"); + } + } else { + EF_INFO("Error: Erased system section fault! Start address is 0x%08X, size is %ld.\n", + get_env_start_addr(), 4); + EF_INFO("Note: The ENV can not be used\n"); + } + return result; +} +#else +static EfErrCode save_cur_using_data_addr(uint32_t cur_data_addr) { + EfErrCode result = EF_NO_ERR; + uint32_t cur_system_sec_addr; + + if (cur_data_addr < get_env_start_addr() + ENV_AREA_SIZE / 2) { + /* current using system section is in ENV area0 */ + cur_system_sec_addr = get_env_start_addr(); + } else { + /* current using system section is in ENV area1 */ + cur_system_sec_addr = get_env_start_addr() + ENV_AREA_SIZE / 2; + } + /* erase ENV system section */ + result = ef_port_erase(cur_system_sec_addr, 4); + if (result == EF_NO_ERR) { + /* write area0 and area1 current using data section address to flash */ + result = ef_port_write(cur_system_sec_addr, &cur_data_addr, 4); + if (result == EF_WRITE_ERR) { + EF_INFO("Error: Write system section fault! Start address is 0x%08X, size is %ld.\n", + cur_system_sec_addr, 4); + EF_INFO("Note: The ENV can not be used.\n"); + } + } else { + EF_INFO("Error: Erased system section fault! Start address is 0x%08X, size is %ld.\n", + cur_system_sec_addr, 4); + EF_INFO("Note: The ENV can not be used\n"); + } + return result; +} +#endif + +/** + * Set and save an ENV. If set ENV is success then will save it. + * + * @param key ENV name + * @param value ENV value + * + * @return result + */ +EfErrCode ef_set_and_save_env(const char *key, const char *value) { + EfErrCode result = EF_NO_ERR; + + result = ef_set_env(key, value); + + if (result == EF_NO_ERR) { + result = ef_save_env(); + } + + return result; +} + +/** + * Del and save an ENV. If del ENV is success then will save it. + * + * @param key ENV name + * + * @return result + */ +EfErrCode ef_del_and_save_env(const char *key) { + EfErrCode result = EF_NO_ERR; + + result = ef_del_env(key); + + if (result == EF_NO_ERR) { + result = ef_save_env(); + } + + return result; +} + +#ifdef EF_ENV_AUTO_UPDATE +/** + * Auto update ENV to latest default when current EF_ENV_VER is changed. + * + * @return result + */ +static EfErrCode env_auto_update(void) +{ + size_t i; + + /* lock the ENV cache */ + ef_port_env_lock(); + + /* read ENV version number from flash*/ + ef_port_read(get_cur_using_data_addr() + ENV_PARAM_INDEX_VER_NUM * 4, + &env_cache[ENV_PARAM_INDEX_VER_NUM] , 4); + + /* check version number */ + if (env_cache[ENV_PARAM_INDEX_VER_NUM] != EF_ENV_VER_NUM) { + env_cache_changed = true; + /* update version number */ + env_cache[ENV_PARAM_INDEX_VER_NUM] = EF_ENV_VER_NUM; + /* add a new ENV when it's not found */ + for (i = 0; i < default_env_set_size; i++) { + if (find_env(default_env_set[i].key) == NULL) { + create_env(default_env_set[i].key, default_env_set[i].value); + } + } + } + + /* unlock the ENV cache */ + ef_port_env_unlock(); + + return ef_save_env(); +} +#endif /* EF_ENV_AUTO_UPDATE */ + +#endif /* EF_ENV_USING_WL_MODE */ + +#endif /* defined(EF_USING_ENV) && defined(EF_ENV_USING_LEGACY_MODE) */ diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_iap.c b/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_iap.c new file mode 100644 index 0000000..b51a483 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_iap.c @@ -0,0 +1,289 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2015-2017, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: IAP(In-Application Programming) operating interface. + * Created on: 2015-01-05 + */ + +#include + +#ifdef EF_USING_IAP + +/* IAP section backup application section start address in flash */ +static uint32_t bak_app_start_addr = 0; + +/** + * Flash IAP function initialize. + * + * @return result + */ +EfErrCode ef_iap_init(void) { + EfErrCode result = EF_NO_ERR; + + bak_app_start_addr = EF_START_ADDR ; + +#if defined(EF_USING_ENV) + bak_app_start_addr += ENV_AREA_SIZE; +#endif + +#if defined(EF_USING_LOG) + bak_app_start_addr += LOG_AREA_SIZE; +#endif + + return result; +} + +/** + * Erase backup area application data. + * + * @param app_size application size + * + * @return result + */ +EfErrCode ef_erase_bak_app(size_t app_size) { + EfErrCode result = EF_NO_ERR; + + result = ef_port_erase(ef_get_bak_app_start_addr(), app_size); + switch (result) { + case EF_NO_ERR: { + EF_INFO("Erased backup area application OK.\n"); + break; + } + case EF_ERASE_ERR: { + EF_INFO("Warning: Erase backup area application fault!\n"); + /* will return when erase fault */ + return result; + } + } + + return result; +} + +/** + * Erase user old application by using specified erase function. + * + * @param user_app_addr application entry address + * @param app_size application size + * @param app_erase user specified application erase function + * + * @return result + */ +EfErrCode ef_erase_spec_user_app(uint32_t user_app_addr, size_t app_size, + EfErrCode (*app_erase)(uint32_t addr, size_t size)) { + EfErrCode result = EF_NO_ERR; + + result = app_erase(user_app_addr, app_size); + switch (result) { + case EF_NO_ERR: { + EF_INFO("Erased user application OK.\n"); + break; + } + case EF_ERASE_ERR: { + EF_INFO("Warning: Erase user application fault!\n"); + /* will return when erase fault */ + return result; + } + } + + return result; +} + +/** + * Erase user old application by using default `ef_port_erase` function. + * + * @param user_app_addr application entry address + * @param app_size application size + * + * @return result + */ +EfErrCode ef_erase_user_app(uint32_t user_app_addr, size_t app_size) { + return ef_erase_spec_user_app(user_app_addr, app_size, ef_port_erase); +} + +/** + * Erase old bootloader + * + * @param bl_addr bootloader entry address + * @param bl_size bootloader size + * + * @return result + */ +EfErrCode ef_erase_bl(uint32_t bl_addr, size_t bl_size) { + EfErrCode result = EF_NO_ERR; + + result = ef_port_erase(bl_addr, bl_size); + switch (result) { + case EF_NO_ERR: { + EF_INFO("Erased bootloader OK.\n"); + break; + } + case EF_ERASE_ERR: { + EF_INFO("Warning: Erase bootloader fault!\n"); + /* will return when erase fault */ + return result; + } + } + + return result; +} + +/** + * Write data of application to backup area. + * + * @param data a part of application + * @param size data size + * @param cur_size current write application size + * @param total_size application total size + * + * @return result + */ +EfErrCode ef_write_data_to_bak(uint8_t *data, size_t size, size_t *cur_size, + size_t total_size) { + EfErrCode result = EF_NO_ERR; + + /* make sure don't write excess data */ + if (*cur_size + size > total_size) { + size = total_size - *cur_size; + } + + result = ef_port_write(ef_get_bak_app_start_addr() + *cur_size, (uint32_t *) data, size); + switch (result) { + case EF_NO_ERR: { + *cur_size += size; + EF_DEBUG("Write data to backup area OK.\n"); + break; + } + case EF_WRITE_ERR: { + EF_INFO("Warning: Write data to backup area fault!\n"); + break; + } + } + + return result; +} + +/** + * Copy backup area application to application entry by using specified write function. + * + * @param user_app_addr application entry address + * @param app_size application size + * @param app_write user specified application write function + * + * @return result + */ +EfErrCode ef_copy_spec_app_from_bak(uint32_t user_app_addr, size_t app_size, + EfErrCode (*app_write)(uint32_t addr, const uint32_t *buf, size_t size)) { + size_t cur_size; + uint32_t app_cur_addr, bak_cur_addr; + EfErrCode result = EF_NO_ERR; + /* 32 words size buffer */ + uint32_t buff[32]; + + /* cycle copy data */ + for (cur_size = 0; cur_size < app_size; cur_size += sizeof(buff)) { + app_cur_addr = user_app_addr + cur_size; + bak_cur_addr = ef_get_bak_app_start_addr() + cur_size; + ef_port_read(bak_cur_addr, buff, sizeof(buff)); + result = app_write(app_cur_addr, buff, sizeof(buff)); + if (result != EF_NO_ERR) { + break; + } + } + + switch (result) { + case EF_NO_ERR: { + EF_INFO("Write data to application entry OK.\n"); + break; + } + case EF_WRITE_ERR: { + EF_INFO("Warning: Write data to application entry fault!\n"); + break; + } + } + + return result; +} + +/** + * Copy backup area application to application entry by using default `ef_port_write` function. + * + * @param user_app_addr application entry address + * @param app_size application size + * + * @return result + */ +EfErrCode ef_copy_app_from_bak(uint32_t user_app_addr, size_t app_size) { + return ef_copy_spec_app_from_bak(user_app_addr, app_size, ef_port_write); +} + +/** + * Copy backup area bootloader to bootloader entry. + * + * @param bl_addr bootloader entry address + * @param bl_size bootloader size + * + * @return result + */ +EfErrCode ef_copy_bl_from_bak(uint32_t bl_addr, size_t bl_size) { + size_t cur_size; + uint32_t bl_cur_addr, bak_cur_addr; + EfErrCode result = EF_NO_ERR; + /* 32 words buffer */ + uint32_t buff[32]; + + /* cycle copy data by 32bytes buffer */ + for (cur_size = 0; cur_size < bl_size; cur_size += sizeof(buff)) { + bl_cur_addr = bl_addr + cur_size; + bak_cur_addr = ef_get_bak_app_start_addr() + cur_size; + ef_port_read(bak_cur_addr, buff, sizeof(buff)); + result = ef_port_write(bl_cur_addr, buff, sizeof(buff)); + if (result != EF_NO_ERR) { + break; + } + } + + switch (result) { + case EF_NO_ERR: { + EF_INFO("Write data to bootloader entry OK.\n"); + break; + } + case EF_WRITE_ERR: { + EF_INFO("Warning: Write data to bootloader entry fault!\n"); + break; + } + } + + return result; +} + +/** + * Get IAP section start address in flash. + * + * @return size + */ +uint32_t ef_get_bak_app_start_addr(void) { + return bak_app_start_addr; +} + +#endif /* EF_USING_IAP */ diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_log.c b/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_log.c new file mode 100644 index 0000000..9d2b8c6 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_log.c @@ -0,0 +1,731 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2015-2019, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: Save logs to flash. + * Created on: 2015-06-04 + */ + +#include + +#ifdef EF_USING_LOG + +#if defined(EF_USING_LOG) && !defined(LOG_AREA_SIZE) +#error "Please configure log area size (in ef_cfg.h)" +#endif + +/* magic code on every sector header. 'EF' is 0xEF30EF30 */ +#define LOG_SECTOR_MAGIC 0xEF30EF30 +/* sector header size, includes the sector magic code and status magic code */ +#define LOG_SECTOR_HEADER_SIZE 12 +/* sector header word size,what is equivalent to the total number of sectors header index */ +#define LOG_SECTOR_HEADER_WORD_SIZE 3 + +/** + * Sector status magic code + * The sector status is 8B after LOG_SECTOR_MAGIC at every sector header. + * ============================================== + * | header(12B) | status | + * ---------------------------------------------- + * | 0xEF30EF30 0xFFFFFFFF 0xFFFFFFFF | empty | + * | 0xEF30EF30 0xFEFEFEFE 0xFFFFFFFF | using | + * | 0xEF30EF30 0xFEFEFEFE 0xFCFCFCFC | full | + * ============================================== + * + * State transition relationship: empty->using->full + * The FULL status will change to EMPTY after sector clean. + */ +#define SECTOR_STATUS_MAGIC_EMPUT 0xFFFFFFFF +#define SECTOR_STATUS_MAGIC_USING 0xFEFEFEFE +#define SECTOR_STATUS_MAGIC_FULL 0xFCFCFCFC + +typedef enum { + SECTOR_STATUS_EMPUT, + SECTOR_STATUS_USING, + SECTOR_STATUS_FULL, + SECTOR_STATUS_HEADER_ERROR, +} SectorStatus; + +typedef enum { + SECTOR_HEADER_MAGIC_INDEX, + SECTOR_HEADER_USING_INDEX, + SECTOR_HEADER_FULL_INDEX, +} SectorHeaderIndex; + +/* the stored logs start address and end address. It's like a ring buffer implemented on flash. */ +static uint32_t log_start_addr = 0, log_end_addr = 0; +/* saved log area address for flash */ +static uint32_t log_area_start_addr = 0; +/* initialize OK flag */ +static bool init_ok = false; + +static void find_start_and_end_addr(void); +static uint32_t get_next_flash_sec_addr(uint32_t cur_addr); + +/** + * The flash save log function initialize. + * + * @return result + */ +EfErrCode ef_log_init(void) { + EfErrCode result = EF_NO_ERR; + + EF_ASSERT(LOG_AREA_SIZE); + EF_ASSERT(EF_ERASE_MIN_SIZE); + /* the log area size must be an integral multiple of erase minimum size. */ + EF_ASSERT(LOG_AREA_SIZE % EF_ERASE_MIN_SIZE == 0); + /* the log area size must be more than twice of EF_ERASE_MIN_SIZE */ + EF_ASSERT(LOG_AREA_SIZE / EF_ERASE_MIN_SIZE >= 2); + +#ifdef EF_USING_ENV + log_area_start_addr = EF_START_ADDR + ENV_AREA_SIZE; +#else + log_area_start_addr = EF_START_ADDR; +#endif + + /* find the log store start address and end address */ + find_start_and_end_addr(); + /* initialize OK */ + init_ok = true; + + return result; +} + +/** + * Get flash sector current status. + * + * @param addr sector address, this function will auto calculate the sector header address by this address. + * + * @return the flash sector current status + */ +static SectorStatus get_sector_status(uint32_t addr) { + uint32_t header_buf[LOG_SECTOR_HEADER_WORD_SIZE] = {0}, header_addr = 0; + uint32_t sector_header_magic = 0; + uint32_t status_full_magic = 0, status_use_magic = 0; + + /* calculate the sector header address */ + header_addr = addr & (~(EF_ERASE_MIN_SIZE - 1)); + + if (ef_port_read(header_addr, header_buf, sizeof(header_buf)) == EF_NO_ERR) { + sector_header_magic = header_buf[SECTOR_HEADER_MAGIC_INDEX]; + status_use_magic = header_buf[SECTOR_HEADER_USING_INDEX]; + status_full_magic = header_buf[SECTOR_HEADER_FULL_INDEX]; + } else { + EF_DEBUG("Error: Read sector header data error.\n"); + return SECTOR_STATUS_HEADER_ERROR; + } + + /* compare header magic code */ + if(sector_header_magic == LOG_SECTOR_MAGIC){ + if((status_use_magic == SECTOR_STATUS_MAGIC_EMPUT) && (status_full_magic == SECTOR_STATUS_MAGIC_EMPUT)) { + return SECTOR_STATUS_EMPUT; + } else if((status_use_magic == SECTOR_STATUS_MAGIC_USING) && (status_full_magic == SECTOR_STATUS_MAGIC_EMPUT)) { + return SECTOR_STATUS_USING; + } else if((status_use_magic == SECTOR_STATUS_MAGIC_USING) && (status_full_magic == SECTOR_STATUS_MAGIC_FULL)) { + return SECTOR_STATUS_FULL; + } else { + return SECTOR_STATUS_HEADER_ERROR; + } + } else { + return SECTOR_STATUS_HEADER_ERROR; + } + +} + +/** + * Write flash sector current status. + * + * @param addr sector address, this function will auto calculate the sector header address by this address. + * @param status sector cur status + * + * @return result + */ +static EfErrCode write_sector_status(uint32_t addr, SectorStatus status) { + uint32_t header, header_addr = 0; + + /* calculate the sector header address */ + header_addr = addr & (~(EF_ERASE_MIN_SIZE - 1)); + + /* calculate the sector staus magic */ + switch (status) { + case SECTOR_STATUS_EMPUT: { + header = LOG_SECTOR_MAGIC; + return ef_port_write(header_addr, &header, sizeof(header)); + } + case SECTOR_STATUS_USING: { + header = SECTOR_STATUS_MAGIC_USING; + return ef_port_write(header_addr + sizeof(header), &header, sizeof(header)); + } + case SECTOR_STATUS_FULL: { + header = SECTOR_STATUS_MAGIC_FULL; + return ef_port_write(header_addr + sizeof(header) * 2, &header, sizeof(header)); + } + default: + return EF_WRITE_ERR; + } +} + +/** + * Find the current flash sector using end address by continuous 0xFF. + * + * @param addr sector address + * + * @return current flash sector using end address + */ +static uint32_t find_sec_using_end_addr(uint32_t addr) { +/* read section data buffer size */ +#define READ_BUF_SIZE 32 + + uint32_t sector_start = addr, data_start = addr, continue_ff = 0, read_buf_size = 0, i; + uint8_t buf[READ_BUF_SIZE]; + + EF_ASSERT(READ_BUF_SIZE % 4 == 0); + /* calculate the sector start and data start address */ + sector_start = addr & (~(EF_ERASE_MIN_SIZE - 1)); + data_start = sector_start + LOG_SECTOR_HEADER_SIZE; + + /* counts continuous 0xFF which is end of sector */ + while (data_start < sector_start + EF_ERASE_MIN_SIZE) { + if (data_start + READ_BUF_SIZE < sector_start + EF_ERASE_MIN_SIZE) { + read_buf_size = READ_BUF_SIZE; + } else { + read_buf_size = sector_start + EF_ERASE_MIN_SIZE - data_start; + } + ef_port_read(data_start, (uint32_t *)buf, read_buf_size); + for (i = 0; i < read_buf_size; i++) { + if (buf[i] == 0xFF) { + continue_ff++; + } else { + continue_ff = 0; + } + } + data_start += read_buf_size; + } + /* calculate current flash sector using end address */ + if (continue_ff >= EF_ERASE_MIN_SIZE - LOG_SECTOR_HEADER_SIZE) { + /* from 0 to sec_size all sector is 0xFF, so the sector is empty */ + return sector_start + LOG_SECTOR_HEADER_SIZE; + } else if (continue_ff >= 4) { + /* form end_addr - 4 to sec_size length all area is 0xFF, so it's used part of the sector. + * the address must be word alignment. */ + if (continue_ff % 4 != 0) { + continue_ff = (continue_ff / 4 + 1) * 4; + } + return sector_start + EF_ERASE_MIN_SIZE - continue_ff; + } else { + /* all sector not has continuous 0xFF, so the sector is full */ + return sector_start + EF_ERASE_MIN_SIZE; + } +} + +/** + * Find the log store start address and end address. + * It's like a ring buffer implemented on flash. + * The flash log area can be in two states depending on start address and end address: + * state 1 state 2 + * |============| |============| + * log area start--> |############| <-- start address |############| <-- end address + * |############| | empty | + * |------------| |------------| + * |############| |############| <-- start address + * |############| |############| + * |------------| |------------| + * | . | | . | + * | . | | . | + * | . | | . | + * |------------| |------------| + * |############| <-- end address |############| + * | empty | |############| + * log area end --> |============| |============| + * + * LOG_AREA_SIZE = log area end - log area star + * + */ +static void find_start_and_end_addr(void) { + size_t cur_size = 0; + SectorStatus cur_sec_status, last_sec_status; + uint32_t cur_using_sec_addr = 0; + /* all status sector counts */ + size_t empty_sec_counts = 0, using_sec_counts = 0, full_sector_counts = 0; + /* total sector number */ + size_t total_sec_num = LOG_AREA_SIZE / EF_ERASE_MIN_SIZE; + /* see comment of find_start_and_end_addr function */ + uint8_t cur_log_sec_state = 0; + + /* get the first sector status */ + cur_sec_status = get_sector_status(log_area_start_addr); + last_sec_status = cur_sec_status; + + for (cur_size = EF_ERASE_MIN_SIZE; cur_size < LOG_AREA_SIZE; cur_size += EF_ERASE_MIN_SIZE) { + /* get current sector status */ + cur_sec_status = get_sector_status(log_area_start_addr + cur_size); + /* compare last and current status */ + switch (last_sec_status) { + case SECTOR_STATUS_EMPUT: { + switch (cur_sec_status) { + case SECTOR_STATUS_EMPUT: + break; + case SECTOR_STATUS_USING: + EF_DEBUG("Error: Log area error! Now will clean all log area.\n"); + ef_log_clean(); + return; + case SECTOR_STATUS_FULL: + EF_DEBUG("Error: Log area error! Now will clean all log area.\n"); + ef_log_clean(); + return; + } + empty_sec_counts++; + break; + } + case SECTOR_STATUS_USING: { + switch (cur_sec_status) { + case SECTOR_STATUS_EMPUT: + /* like state 1 */ + cur_log_sec_state = 1; + log_start_addr = log_area_start_addr; + cur_using_sec_addr = log_area_start_addr + cur_size - EF_ERASE_MIN_SIZE; + break; + case SECTOR_STATUS_USING: + EF_DEBUG("Error: Log area error! Now will clean all log area.\n"); + ef_log_clean(); + return; + case SECTOR_STATUS_FULL: + /* like state 2 */ + cur_log_sec_state = 2; + log_start_addr = log_area_start_addr + cur_size; + cur_using_sec_addr = log_area_start_addr + cur_size - EF_ERASE_MIN_SIZE; + break; + } + using_sec_counts++; + break; + } + case SECTOR_STATUS_FULL: { + switch (cur_sec_status) { + case SECTOR_STATUS_EMPUT: + /* like state 1 */ + if (cur_log_sec_state == 2) { + EF_DEBUG("Error: Log area error! Now will clean all log area.\n"); + ef_log_clean(); + return; + } else { + cur_log_sec_state = 1; + log_start_addr = log_area_start_addr; + log_end_addr = log_area_start_addr + cur_size; + cur_using_sec_addr = log_area_start_addr + cur_size - EF_ERASE_MIN_SIZE; + } + break; + case SECTOR_STATUS_USING: + if(total_sec_num <= 2) { + /* like state 1 */ + cur_log_sec_state = 1; + log_start_addr = log_area_start_addr; + cur_using_sec_addr = log_area_start_addr + cur_size; + } else { + /* like state 2 when the sector is the last one */ + if (cur_size + EF_ERASE_MIN_SIZE >= LOG_AREA_SIZE) { + cur_log_sec_state = 2; + log_start_addr = get_next_flash_sec_addr(log_area_start_addr + cur_size); + cur_using_sec_addr = log_area_start_addr + cur_size; + } + } + break; + case SECTOR_STATUS_FULL: + break; + } + full_sector_counts++; + break; + } + case SECTOR_STATUS_HEADER_ERROR: + EF_DEBUG("Error: Log sector header error! Now will clean all log area.\n"); + ef_log_clean(); + return; + } + last_sec_status = cur_sec_status; + } + + /* the last sector status counts */ + if (cur_sec_status == SECTOR_STATUS_EMPUT) { + empty_sec_counts++; + } else if (cur_sec_status == SECTOR_STATUS_USING) { + using_sec_counts++; + } else if (cur_sec_status == SECTOR_STATUS_FULL) { + full_sector_counts++; + } else if (cur_sec_status == SECTOR_STATUS_HEADER_ERROR) { + EF_DEBUG("Error: Log sector header error! Now will clean all log area.\n"); + ef_log_clean(); + return; + } + + if (using_sec_counts != 1) { + /* this state is almost impossible */ + EF_DEBUG("Error: There must be only one sector status is USING! Now will clean all log area.\n"); + ef_log_clean(); + } else { + /* find the end address */ + log_end_addr = find_sec_using_end_addr(cur_using_sec_addr); + } + +} + +/** + * Get log used flash total size. + * + * @return log used flash total size. @note NOT contain sector headers + */ +size_t ef_log_get_used_size(void) { + size_t header_total_num = 0, physical_size = 0; + /* must be call this function after initialize OK */ + if (!init_ok) { + return 0; + } + + if (log_start_addr < log_end_addr) { + physical_size = log_end_addr - log_start_addr; + } else { + physical_size = LOG_AREA_SIZE - (log_start_addr - log_end_addr); + } + + header_total_num = physical_size / EF_ERASE_MIN_SIZE + 1; + + return physical_size - header_total_num * LOG_SECTOR_HEADER_SIZE; +} + +/** + * Sequential reading log data. It will ignore sector headers. + * + * @param addr address + * @param log log buffer + * @param size log size, not contain sector headers. + * + * @return result + */ +static EfErrCode log_seq_read(uint32_t addr, uint32_t *log, size_t size) { + EfErrCode result = EF_NO_ERR; + size_t read_size = 0, read_size_temp = 0; + + while (size) { + /* move to sector data address */ + if ((addr + read_size) % EF_ERASE_MIN_SIZE == 0) { + addr += LOG_SECTOR_HEADER_SIZE; + } + /* calculate current sector last data size */ + read_size_temp = EF_ERASE_MIN_SIZE - (addr % EF_ERASE_MIN_SIZE); + if (size < read_size_temp) { + read_size_temp = size; + } + result = ef_port_read(addr + read_size, log + read_size / 4, read_size_temp); + if (result != EF_NO_ERR) { + return result; + } + read_size += read_size_temp; + size -= read_size_temp; + } + + return result; +} + +/** + * Calculate flash physical address by log index. + * + * @param index log index + * + * @return flash physical address + */ +static uint32_t log_index2addr(size_t index) { + size_t header_total_offset = 0; + /* total include sector number */ + size_t sector_num = index / (EF_ERASE_MIN_SIZE - LOG_SECTOR_HEADER_SIZE) + 1; + + header_total_offset = sector_num * LOG_SECTOR_HEADER_SIZE; + if (log_start_addr < log_end_addr) { + return log_start_addr + index + header_total_offset; + } else { + if (log_start_addr + index + header_total_offset < log_area_start_addr + LOG_AREA_SIZE) { + return log_start_addr + index + header_total_offset; + } else { + return log_start_addr + index + header_total_offset - LOG_AREA_SIZE; + + } + } +} + +/** + * Read log from flash. + * + * @param index index for saved log. + * Minimum index is 0. + * Maximum index is ef_log_get_used_size() - 1. + * @param log the log which will read from flash + * @param size read bytes size + * + * @return result + */ +EfErrCode ef_log_read(size_t index, uint32_t *log, size_t size) { + EfErrCode result = EF_NO_ERR; + size_t cur_using_size = ef_log_get_used_size(); + size_t read_size_temp = 0; + size_t header_total_num = 0; + + if (!size) { + return result; + } + + EF_ASSERT(size % 4 == 0); + EF_ASSERT(index < cur_using_size); + + if (index + size > cur_using_size) { + EF_DEBUG("Warning: Log read size out of bound. Cut read size.\n"); + size = cur_using_size - index; + } + /* must be call this function after initialize OK */ + if (!init_ok) { + return EF_ENV_INIT_FAILED; + } + + if (log_start_addr < log_end_addr) { + log_seq_read(log_index2addr(index), log, size); + } else { + if (log_index2addr(index) + size <= log_area_start_addr + LOG_AREA_SIZE) { + /* Flash log area + * |--------------| + * log_area_start_addr --> |##############| + * |##############| + * |##############| + * |--------------| + * |##############| + * |##############| + * |##############| <-- log_end_addr + * |--------------| + * log_start_addr --> |##############| + * read start --> |**************| <-- read end + * |##############| + * |--------------| + * + * read from (log_start_addr + log_index2addr(index)) to (log_start_addr + index + log_index2addr(index)) + */ + result = log_seq_read(log_index2addr(index), log, size); + } else if (log_index2addr(index) < log_area_start_addr + LOG_AREA_SIZE) { + /* Flash log area + * |--------------| + * log_area_start_addr --> |**************| <-- read end + * |##############| + * |##############| + * |--------------| + * |##############| + * |##############| + * |##############| <-- log_end_addr + * |--------------| + * log_start_addr --> |##############| + * read start --> |**************| + * |**************| + * |--------------| + * read will by 2 steps + * step1: read from (log_start_addr + log_index2addr(index)) to flash log area end address + * step2: read from flash log area start address to read size's end address + */ + read_size_temp = (log_area_start_addr + LOG_AREA_SIZE) - log_index2addr(index); + header_total_num = read_size_temp / EF_ERASE_MIN_SIZE; + /* Minus some ignored bytes */ + read_size_temp -= header_total_num * LOG_SECTOR_HEADER_SIZE; + result = log_seq_read(log_index2addr(index), log, read_size_temp); + if (result == EF_NO_ERR) { + result = log_seq_read(log_area_start_addr, log + read_size_temp / 4, size - read_size_temp); + } + } else { + /* Flash log area + * |--------------| + * log_area_start_addr --> |##############| + * read start --> |**************| + * |**************| <-- read end + * |--------------| + * |##############| + * |##############| + * |##############| <-- log_end_addr + * |--------------| + * log_start_addr --> |##############| + * |##############| + * |##############| + * |--------------| + * read from (log_start_addr + log_index2addr(index) - LOG_AREA_SIZE) to read size's end address + */ + result = log_seq_read(log_index2addr(index) - LOG_AREA_SIZE, log, size); + } + } + + return result; +} + +/** + * Write log to flash. + * + * @param log the log which will be write to flash + * @param size write bytes size + * + * @return result + */ +EfErrCode ef_log_write(const uint32_t *log, size_t size) { + EfErrCode result = EF_NO_ERR; + size_t write_size = 0, writable_size = 0; + uint32_t write_addr = log_end_addr, erase_addr; + SectorStatus sector_status; + + EF_ASSERT(size % 4 == 0); + /* must be call this function after initialize OK */ + if (!init_ok) { + return EF_ENV_INIT_FAILED; + } + + if ((sector_status = get_sector_status(write_addr)) == SECTOR_STATUS_HEADER_ERROR) { + return EF_WRITE_ERR; + } + /* write some log when current sector status is USING and EMPTY */ + if ((sector_status == SECTOR_STATUS_USING) || (sector_status == SECTOR_STATUS_EMPUT)) { + /* write the already erased but not used area */ + writable_size = EF_ERASE_MIN_SIZE - ((write_addr - log_area_start_addr) % EF_ERASE_MIN_SIZE); + if (size >= writable_size) { + result = ef_port_write(write_addr, log, writable_size); + if (result != EF_NO_ERR) { + goto exit; + } + /* change the current sector status to FULL */ + result = write_sector_status(write_addr, SECTOR_STATUS_FULL); + if (result != EF_NO_ERR) { + goto exit; + } + write_size += writable_size; + } else { + result = ef_port_write(write_addr, log, size); + log_end_addr = write_addr + size; + goto exit; + } + } + /* erase and write remain log */ + while (true) { + /* calculate next available sector address */ + erase_addr = write_addr = get_next_flash_sec_addr(write_addr - 4); + /* move the flash log start address to next available sector address */ + if (log_start_addr == erase_addr) { + log_start_addr = get_next_flash_sec_addr(log_start_addr); + } + /* erase sector */ + result = ef_port_erase(erase_addr, EF_ERASE_MIN_SIZE); + if (result != EF_NO_ERR) { + goto exit; + } + /* change the sector status to EMPTY and USING when write begin sector start address */ + result = write_sector_status(write_addr, SECTOR_STATUS_EMPUT); + result = write_sector_status(write_addr, SECTOR_STATUS_USING); + if (result == EF_NO_ERR) { + write_addr += LOG_SECTOR_HEADER_SIZE; + } else { + goto exit; + } + /* calculate current sector writable data size */ + writable_size = EF_ERASE_MIN_SIZE - LOG_SECTOR_HEADER_SIZE; + if (size - write_size >= writable_size) { + result = ef_port_write(write_addr, log + write_size / 4, writable_size); + if (result != EF_NO_ERR) { + goto exit; + } + /* change the current sector status to FULL */ + result = write_sector_status(write_addr, SECTOR_STATUS_FULL); + if (result != EF_NO_ERR) { + goto exit; + } + log_end_addr = write_addr + writable_size; + write_size += writable_size; + write_addr += writable_size; + } else { + result = ef_port_write(write_addr, log + write_size / 4, size - write_size); + if (result != EF_NO_ERR) { + goto exit; + } + log_end_addr = write_addr + (size - write_size); + break; + } + } + +exit: + return result; +} + +/** + * Get next flash sector address.The log total sector like ring buffer which implement by flash. + * + * @param cur_addr cur flash address + * + * @return next flash sector address + */ +static uint32_t get_next_flash_sec_addr(uint32_t cur_addr) { + size_t cur_sec_id = (cur_addr - log_area_start_addr) / EF_ERASE_MIN_SIZE; + size_t sec_total_num = LOG_AREA_SIZE / EF_ERASE_MIN_SIZE; + + if (cur_sec_id + 1 >= sec_total_num) { + /* return to ring head */ + return log_area_start_addr; + } else { + return log_area_start_addr + (cur_sec_id + 1) * EF_ERASE_MIN_SIZE; + } +} + +/** + * Clean all log which in flash. + * + * @return result + */ +EfErrCode ef_log_clean(void) { + EfErrCode result = EF_NO_ERR; + uint32_t write_addr = log_area_start_addr; + + /* clean address */ + log_start_addr = log_area_start_addr; + log_end_addr = log_start_addr + LOG_SECTOR_HEADER_SIZE; + /* erase log flash area */ + result = ef_port_erase(log_area_start_addr, LOG_AREA_SIZE); + if (result != EF_NO_ERR) { + goto exit; + } + /* setting first sector is EMPTY to USING */ + write_sector_status(write_addr, SECTOR_STATUS_EMPUT); + write_sector_status(write_addr, SECTOR_STATUS_USING); + if (result != EF_NO_ERR) { + goto exit; + } + write_addr += EF_ERASE_MIN_SIZE; + /* add sector header */ + while (true) { + write_sector_status(write_addr, SECTOR_STATUS_EMPUT); + if (result != EF_NO_ERR) { + goto exit; + } + write_addr += EF_ERASE_MIN_SIZE; + if (write_addr >= log_area_start_addr + LOG_AREA_SIZE) { + break; + } + } + +exit: + return result; +} + +#endif /* EF_USING_LOG */ diff --git a/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_utils.c b/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_utils.c new file mode 100644 index 0000000..c6c9571 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easyflash/src/ef_utils.c @@ -0,0 +1,99 @@ +/* + * This file is part of the EasyFlash Library. + * + * Copyright (c) 2015-2017, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: Some utils for this library. + * Created on: 2015-01-14 + */ + +#include + +static const uint32_t crc32_table[] = +{ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +/** + * Calculate the CRC32 value of a memory buffer. + * + * @param crc accumulated CRC32 value, must be 0 on first call + * @param buf buffer to calculate CRC32 value for + * @param size bytes in buffer + * + * @return calculated CRC32 value + */ +uint32_t ef_calc_crc32(uint32_t crc, const void *buf, size_t size) +{ + const uint8_t *p; + + p = (const uint8_t *)buf; + crc = crc ^ ~0U; + + while (size--) { + crc = crc32_table[(crc ^ *p++) & 0xFF] ^ (crc >> 8); + } + + return crc ^ ~0U; +} diff --git a/demo/os/nuttx-spiflash/apps/system/easylogger/Kconfig b/demo/os/nuttx-spiflash/apps/system/easylogger/Kconfig new file mode 100644 index 0000000..8dcd6c2 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easylogger/Kconfig @@ -0,0 +1,27 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +menuconfig SYSTEM_EASYLOGGER + tristate "Easylogger" + default n + ---help--- + Enable support for the Easylogger + +if SYSTEM_EASYLOGGER + +config SYSTEM_EASYLOGGER_FILE + bool "Enable EasyLogger File" + default n + ---help--- + Add EasyLogger File support. + +config SYSTEM_EASYLOGGER_FLASH + bool "Enable EasyLogger Flash" + default n + select SYSTEM_EASYFLASH + ---help--- + Add EasyLogger Flash support. + +endif # SYSTEM_EASYLOGGER diff --git a/demo/os/nuttx-spiflash/apps/system/easylogger/Make.defs b/demo/os/nuttx-spiflash/apps/system/easylogger/Make.defs new file mode 100644 index 0000000..f78b24c --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easylogger/Make.defs @@ -0,0 +1,23 @@ +############################################################################ +# apps/system/easylogger/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_SYSTEM_EASYLOGGER),) +CONFIGURED_APPS += $(APPDIR)/system/easylogger +endif diff --git a/demo/os/nuttx-spiflash/apps/system/easylogger/Makefile b/demo/os/nuttx-spiflash/apps/system/easylogger/Makefile new file mode 100644 index 0000000..6d18bd6 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easylogger/Makefile @@ -0,0 +1,45 @@ +############################################################################ +# apps/system/easylogger/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +# easylogger Application + +CSRCS = elog_port.c elog.c elog_utils.c +CSRCS += elog_async.c elog_buf.c + +CFLAGS += ${shell $(INCDIR) "$(CC)" $(APPDIR)/system/easylogger/inc} + +VPATH += :src port + +ifeq ($(CONFIG_SYSTEM_EASYLOGGER_FILE),y) +CSRCS += elog_file.c elog_file_port.c +CFLAGS += ${shell $(INCDIR) "$(CC)" $(APPDIR)/system/easylogger/plugins/file} +VPATH += :plugins/file +endif + +ifeq ($(CONFIG_SYSTEM_EASYLOGGER_FLASH),y) +CSRCS += elog_flash.c elog_flash_port.c +CFLAGS += ${shell $(INCDIR) "$(CC)" $(APPDIR)/system/easylogger/plugins/flash} +VPATH += :plugins/flash +CFLAGS += ${shell $(INCDIR) "$(CC)" $(APPDIR)/system/easyflash/inc} +endif + +include $(APPDIR)/Application.mk diff --git a/demo/os/nuttx-spiflash/apps/system/easylogger/inc/elog_cfg.h b/demo/os/nuttx-spiflash/apps/system/easylogger/inc/elog_cfg.h new file mode 100644 index 0000000..b484204 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easylogger/inc/elog_cfg.h @@ -0,0 +1,86 @@ +/* + * This file is part of the EasyLogger Library. + * + * Copyright (c) 2015-2016, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: It is the configure head file for this library. + * Created on: 2015-07-30 + */ + +#ifndef _ELOG_CFG_H_ +#define _ELOG_CFG_H_ +/*---------------------------------------------------------------------------*/ +/* enable log output. */ +#define ELOG_OUTPUT_ENABLE +/* enable terminal output. default open this macro */ +#define ELOG_TERMINAL_ENABLE +/*---------------------------------------------------------------------------*/ +/* enable log write file. default open this macro */ +// #define ELOG_FILE_ENABLE +/* enable flush file cache. default open this macro */ +// #define ELOG_FILE_FLUSH_CACHE_ENABLE +/*---------------------------------------------------------------------------*/ +/* setting static output log level. range: from ELOG_LVL_ASSERT to ELOG_LVL_VERBOSE */ +#define ELOG_OUTPUT_LVL ELOG_LVL_VERBOSE +/* enable assert check */ +#define ELOG_ASSERT_ENABLE +/* buffer size for every line's log */ +#define ELOG_LINE_BUF_SIZE 160 +/* output line number max length */ +#define ELOG_LINE_NUM_MAX_LEN 5 +/* output filter's tag max length */ +#define ELOG_FILTER_TAG_MAX_LEN 16 +/* output filter's keyword max length */ +#define ELOG_FILTER_KW_MAX_LEN 16 +/* output filter's tag level max num */ +#define ELOG_FILTER_TAG_LVL_MAX_NUM 5 +/* output newline sign */ +#define ELOG_NEWLINE_SIGN "\n" +/*---------------------------------------------------------------------------*/ +/* enable log color */ +// #define ELOG_COLOR_ENABLE +/* change the some level logs to not default color if you want */ +#define ELOG_COLOR_ASSERT (F_MAGENTA B_NULL S_NORMAL) +#define ELOG_COLOR_ERROR (F_RED B_NULL S_NORMAL) +#define ELOG_COLOR_WARN (F_YELLOW B_NULL S_NORMAL) +#define ELOG_COLOR_INFO (F_CYAN B_NULL S_NORMAL) +#define ELOG_COLOR_DEBUG (F_GREEN B_NULL S_NORMAL) +#define ELOG_COLOR_VERBOSE (F_BLUE B_NULL S_NORMAL) +/*---------------------------------------------------------------------------*/ +/* enable asynchronous output mode */ +#define ELOG_ASYNC_OUTPUT_ENABLE +/* the highest output level for async mode, other level will sync output */ +#define ELOG_ASYNC_OUTPUT_LVL ELOG_LVL_DEBUG +/* buffer size for asynchronous output mode */ +#define ELOG_ASYNC_OUTPUT_BUF_SIZE (ELOG_LINE_BUF_SIZE * 10) +/* each asynchronous output's log which must end with newline sign */ +//#define ELOG_ASYNC_LINE_OUTPUT +/* asynchronous output mode using POSIX pthread implementation */ +#define ELOG_ASYNC_OUTPUT_USING_PTHREAD +/*---------------------------------------------------------------------------*/ +/* enable buffered output mode */ +// #define ELOG_BUF_OUTPUT_ENABLE +/* buffer size for buffered output mode */ +// #define ELOG_BUF_OUTPUT_BUF_SIZE (ELOG_LINE_BUF_SIZE * 10) +/*---------------------------------------------------------------------------*/ + +#endif /* _ELOG_CFG_H_ */ diff --git a/demo/os/nuttx-spiflash/apps/system/easylogger/plugins/flash/elog_flash_cfg.h b/demo/os/nuttx-spiflash/apps/system/easylogger/plugins/flash/elog_flash_cfg.h new file mode 100644 index 0000000..ec750e6 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easylogger/plugins/flash/elog_flash_cfg.h @@ -0,0 +1,37 @@ +/* + * This file is part of the EasyLogger Library. + * + * Copyright (c) 2015, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: It is the configure head file for this flash log plugin. + * Created on: 2015-07-30 + */ + +#ifndef _ELOG_FLASH_CFG_H_ +#define _ELOG_FLASH_CFG_H_ + +/* EasyLogger flash log plugin's using buffer mode */ +#define ELOG_FLASH_USING_BUF_MODE +/* EasyLogger flash log plugin's RAM buffer size */ +#define ELOG_FLASH_BUF_SIZE 1024 /* @note you must define it for a value */ + +#endif /* _ELOG_FLASH_CFG_H_ */ diff --git a/demo/os/nuttx-spiflash/apps/system/easylogger/plugins/flash/elog_flash_port.c b/demo/os/nuttx-spiflash/apps/system/easylogger/plugins/flash/elog_flash_port.c new file mode 100644 index 0000000..45ba4d4 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easylogger/plugins/flash/elog_flash_port.c @@ -0,0 +1,80 @@ +/* + * This file is part of the EasyLogger Library. + * + * Copyright (c) 2015, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: Portable interface for EasyLogger's flash log pulgin. + * Created on: 2015-07-28 + */ + +#include + +#include "elog_flash.h" + +static pthread_mutex_t flash_log_lock; + +/** + * EasyLogger flash log pulgin port initialize + * + * @return result + */ +ElogErrCode elog_flash_port_init(void) { + ElogErrCode result = ELOG_NO_ERR; + + /* add your code here */ + pthread_mutex_init(&flash_log_lock, NULL); + + return result; +} + +/** + * output flash saved log port interface + * + * @param log flash saved log + * @param size log size + */ +void elog_flash_port_output(const char *log, size_t size) { + + /* add your code here */ + printf("%.*s", size, log); + +} + +/** + * flash log lock + */ +void elog_flash_port_lock(void) { + + /* add your code here */ + pthread_mutex_lock(&flash_log_lock); + +} + +/** + * flash log unlock + */ +void elog_flash_port_unlock(void) { + + /* add your code here */ + pthread_mutex_unlock(&flash_log_lock); + +} \ No newline at end of file diff --git a/demo/os/nuttx-spiflash/apps/system/easylogger/port/elog_port.c b/demo/os/nuttx-spiflash/apps/system/easylogger/port/elog_port.c new file mode 100644 index 0000000..412c5c5 --- /dev/null +++ b/demo/os/nuttx-spiflash/apps/system/easylogger/port/elog_port.c @@ -0,0 +1,168 @@ +/* + * This file is part of the EasyLogger Library. + * + * Copyright (c) 2015, Armink, + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * 'Software'), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Function: Portable interface for linux. + * Created on: 2015-04-28 + */ + +#include +#include +#include +#include +#include +#include + +#ifdef ELOG_FILE_ENABLE +#include +#endif + +static pthread_mutex_t output_lock; + +/** + * EasyLogger port initialize + * + * @return result + */ +ElogErrCode elog_port_init(void) { + ElogErrCode result = ELOG_NO_ERR; + + /* add your code here */ + pthread_mutex_init(&output_lock, NULL); + +#ifdef ELOG_FILE_ENABLE + elog_file_init(); +#endif + + return result; +} + +/** + * EasyLogger port deinitialize + * + */ +void elog_port_deinit(void) { + + /* add your code here */ + +#ifdef ELOG_FILE_ENABLE + elog_file_deinit(); +#endif + + pthread_mutex_destroy(&output_lock); +} + +/** + * output log port interface + * + * @param log output of log + * @param size log size + */ +void elog_port_output(const char *log, size_t size) { + + /* add your code here */ + + /* output to terminal */ +#ifdef ELOG_TERMINAL_ENABLE + printf("%.*s", (int)size, log); +#endif + +#ifdef ELOG_FILE_ENABLE + /* write the file */ + elog_file_write(log, size); +#endif +} + +/** + * output lock + */ +void elog_port_output_lock(void) { + + /* add your code here */ + + pthread_mutex_lock(&output_lock); +} + +/** + * output unlock + */ +void elog_port_output_unlock(void) { + + /* add your code here */ + + pthread_mutex_unlock(&output_lock); +} + +/** + * get current time interface + * + * @return current time + */ +const char *elog_port_get_time(void) { + + /* add your code here */ + + static char cur_system_time[24] = { 0 }; + + time_t cur_t; + struct tm cur_tm; + + time(&cur_t); + localtime_r(&cur_t, &cur_tm); + + strftime(cur_system_time, sizeof(cur_system_time), "%Y-%m-%d %T", &cur_tm); + + return cur_system_time; +} + +/** + * get current process name interface + * + * @return current process name + */ +const char *elog_port_get_p_info(void) { + + /* add your code here */ + + static char cur_process_info[10] = { 0 }; + + snprintf(cur_process_info, 10, "pid:%04d", getpid()); + + return cur_process_info; +} + +/** + * get current thread name interface + * + * @return current thread name + */ +const char *elog_port_get_t_info(void) { + + /* add your code here */ + + static char cur_thread_info[10] = { 0 }; + + snprintf(cur_thread_info, 10, "tid:%04d", gettid()); + + return cur_thread_info; +} diff --git a/docs/zh/images/ElogNuttxSpiFlashDemo.png b/docs/zh/images/ElogNuttxSpiFlashDemo.png new file mode 100644 index 0000000000000000000000000000000000000000..a8bf0372d38aaed5a2c73c9ec0598d4fe19c59f3 GIT binary patch literal 96352 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ⅈ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