|  | /* | 
|  | * Copyright (c) 2019 Laczen | 
|  | * Copyright (c) 2018 Nordic Semiconductor ASA | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT zephyr_sim_eeprom | 
|  |  | 
|  | #ifdef CONFIG_ARCH_POSIX | 
|  | #include "eeprom_simulator_native.h" | 
|  | #include "cmdline.h" | 
|  | #include "soc.h" | 
|  | #endif /* CONFIG_ARCH_POSIX */ | 
|  |  | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/drivers/eeprom.h> | 
|  |  | 
|  | #include <zephyr/init.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/sys/util.h> | 
|  | #include <zephyr/stats/stats.h> | 
|  | #include <string.h> | 
|  | #include <errno.h> | 
|  |  | 
|  | #define LOG_LEVEL CONFIG_EEPROM_LOG_LEVEL | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(eeprom_simulator); | 
|  |  | 
|  | struct eeprom_sim_config { | 
|  | size_t size; | 
|  | bool readonly; | 
|  | }; | 
|  |  | 
|  | #define EEPROM(addr) (mock_eeprom + (addr)) | 
|  |  | 
|  | #if defined(CONFIG_MULTITHREADING) | 
|  | /* semaphore for locking flash resources (tickers) */ | 
|  | static struct k_sem sem_lock; | 
|  | #define SYNC_INIT() k_sem_init(&sem_lock, 1, 1) | 
|  | #define SYNC_LOCK() k_sem_take(&sem_lock, K_FOREVER) | 
|  | #define SYNC_UNLOCK() k_sem_give(&sem_lock) | 
|  | #else | 
|  | #define SYNC_INIT() | 
|  | #define SYNC_LOCK() | 
|  | #define SYNC_UNLOCK() | 
|  | #endif | 
|  |  | 
|  | /* simulator statistics */ | 
|  | STATS_SECT_START(eeprom_sim_stats) | 
|  | STATS_SECT_ENTRY32(bytes_read)		/* total bytes read */ | 
|  | STATS_SECT_ENTRY32(bytes_written)	/* total bytes written */ | 
|  | STATS_SECT_ENTRY32(eeprom_read_calls)	/* calls to eeprom_read() */ | 
|  | STATS_SECT_ENTRY32(eeprom_read_time_us) /* time spent in eeprom_read() */ | 
|  | STATS_SECT_ENTRY32(eeprom_write_calls)  /* calls to eeprom_write() */ | 
|  | STATS_SECT_ENTRY32(eeprom_write_time_us)/* time spent in eeprom_write() */ | 
|  | STATS_SECT_END; | 
|  |  | 
|  | STATS_SECT_DECL(eeprom_sim_stats) eeprom_sim_stats; | 
|  | STATS_NAME_START(eeprom_sim_stats) | 
|  | STATS_NAME(eeprom_sim_stats, bytes_read) | 
|  | STATS_NAME(eeprom_sim_stats, bytes_written) | 
|  | STATS_NAME(eeprom_sim_stats, eeprom_read_calls) | 
|  | STATS_NAME(eeprom_sim_stats, eeprom_read_time_us) | 
|  | STATS_NAME(eeprom_sim_stats, eeprom_write_calls) | 
|  | STATS_NAME(eeprom_sim_stats, eeprom_write_time_us) | 
|  | STATS_NAME_END(eeprom_sim_stats); | 
|  |  | 
|  | /* simulator dynamic thresholds */ | 
|  | STATS_SECT_START(eeprom_sim_thresholds) | 
|  | STATS_SECT_ENTRY32(max_write_calls) | 
|  | STATS_SECT_ENTRY32(max_len) | 
|  | STATS_SECT_END; | 
|  |  | 
|  | STATS_SECT_DECL(eeprom_sim_thresholds) eeprom_sim_thresholds; | 
|  | STATS_NAME_START(eeprom_sim_thresholds) | 
|  | STATS_NAME(eeprom_sim_thresholds, max_write_calls) | 
|  | STATS_NAME(eeprom_sim_thresholds, max_len) | 
|  | STATS_NAME_END(eeprom_sim_thresholds); | 
|  |  | 
|  | #ifdef CONFIG_ARCH_POSIX | 
|  | static char *mock_eeprom; | 
|  | static int eeprom_fd = -1; | 
|  | static const char *eeprom_file_path; | 
|  | #define DEFAULT_EEPROM_FILE_PATH "eeprom.bin" | 
|  | static bool eeprom_erase_at_start; | 
|  | static bool eeprom_rm_at_exit; | 
|  | static bool eeprom_in_ram; | 
|  | #else | 
|  | static uint8_t mock_eeprom[DT_INST_PROP(0, size)]; | 
|  | #endif /* CONFIG_ARCH_POSIX */ | 
|  |  | 
|  | static int eeprom_range_is_valid(const struct device *dev, off_t offset, | 
|  | size_t len) | 
|  | { | 
|  | const struct eeprom_sim_config *config = dev->config; | 
|  |  | 
|  | if ((offset + len) <= config->size) { | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int eeprom_sim_read(const struct device *dev, off_t offset, void *data, | 
|  | size_t len) | 
|  | { | 
|  | if (!len) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (!eeprom_range_is_valid(dev, offset, len)) { | 
|  | LOG_WRN("attempt to read past device boundary"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | SYNC_LOCK(); | 
|  |  | 
|  | STATS_INC(eeprom_sim_stats, eeprom_read_calls); | 
|  | memcpy(data, EEPROM(offset), len); | 
|  | STATS_INCN(eeprom_sim_stats, bytes_read, len); | 
|  |  | 
|  | SYNC_UNLOCK(); | 
|  |  | 
|  | #ifdef CONFIG_EEPROM_SIMULATOR_SIMULATE_TIMING | 
|  | k_busy_wait(CONFIG_EEPROM_SIMULATOR_MIN_READ_TIME_US); | 
|  | STATS_INCN(eeprom_sim_stats, eeprom_read_time_us, | 
|  | CONFIG_EEPROM_SIMULATOR_MIN_READ_TIME_US); | 
|  | #endif | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int eeprom_sim_write(const struct device *dev, off_t offset, | 
|  | const void *data, | 
|  | size_t len) | 
|  | { | 
|  | const struct eeprom_sim_config *config = dev->config; | 
|  |  | 
|  | if (config->readonly) { | 
|  | LOG_WRN("attempt to write to read-only device"); | 
|  | return -EACCES; | 
|  | } | 
|  |  | 
|  | if (!len) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (!eeprom_range_is_valid(dev, offset, len)) { | 
|  | LOG_WRN("attempt to write past device boundary"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | SYNC_LOCK(); | 
|  |  | 
|  | STATS_INC(eeprom_sim_stats, eeprom_write_calls); | 
|  |  | 
|  | bool data_part_ignored = false; | 
|  |  | 
|  | if (eeprom_sim_thresholds.max_write_calls != 0) { | 
|  | if (eeprom_sim_stats.eeprom_write_calls > | 
|  | eeprom_sim_thresholds.max_write_calls) { | 
|  | goto end; | 
|  | } else if (eeprom_sim_stats.eeprom_write_calls == | 
|  | eeprom_sim_thresholds.max_write_calls) { | 
|  | if (eeprom_sim_thresholds.max_len == 0) { | 
|  | goto end; | 
|  | } | 
|  |  | 
|  | data_part_ignored = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if ((data_part_ignored) && (len > eeprom_sim_thresholds.max_len)) { | 
|  | len = eeprom_sim_thresholds.max_len; | 
|  | } | 
|  |  | 
|  | memcpy(EEPROM(offset), data, len); | 
|  |  | 
|  | STATS_INCN(eeprom_sim_stats, bytes_written, len); | 
|  |  | 
|  | #ifdef CONFIG_EEPROM_SIMULATOR_SIMULATE_TIMING | 
|  | /* wait before returning */ | 
|  | k_busy_wait(CONFIG_EEPROM_SIMULATOR_MIN_WRITE_TIME_US); | 
|  | STATS_INCN(eeprom_sim_stats, eeprom_write_time_us, | 
|  | CONFIG_EEPROM_SIMULATOR_MIN_WRITE_TIME_US); | 
|  | #endif | 
|  |  | 
|  | end: | 
|  | SYNC_UNLOCK(); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static size_t eeprom_sim_size(const struct device *dev) | 
|  | { | 
|  | const struct eeprom_sim_config *config = dev->config; | 
|  |  | 
|  | return config->size; | 
|  | } | 
|  |  | 
|  | static DEVICE_API(eeprom, eeprom_sim_api) = { | 
|  | .read = eeprom_sim_read, | 
|  | .write = eeprom_sim_write, | 
|  | .size = eeprom_sim_size, | 
|  | }; | 
|  |  | 
|  | static const struct eeprom_sim_config eeprom_sim_config_0 = { | 
|  | .size = DT_INST_PROP(0, size), | 
|  | .readonly = DT_INST_PROP(0, read_only), | 
|  | }; | 
|  |  | 
|  | #ifdef CONFIG_ARCH_POSIX | 
|  |  | 
|  | static int eeprom_mock_init(const struct device *dev) | 
|  | { | 
|  | int rc; | 
|  |  | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | if (eeprom_in_ram == false && eeprom_file_path == NULL) { | 
|  | eeprom_file_path = DEFAULT_EEPROM_FILE_PATH; | 
|  | } | 
|  |  | 
|  | rc = eeprom_mock_init_native(eeprom_in_ram, &mock_eeprom, DT_INST_PROP(0, size), &eeprom_fd, | 
|  | eeprom_file_path, 0xFF, eeprom_erase_at_start); | 
|  |  | 
|  | if (rc < 0) { | 
|  | return -EIO; | 
|  | } else { | 
|  | return 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | #else | 
|  |  | 
|  | static int eeprom_mock_init(const struct device *dev) | 
|  | { | 
|  | memset(mock_eeprom, 0xFF, ARRAY_SIZE(mock_eeprom)); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #endif /* CONFIG_ARCH_POSIX */ | 
|  |  | 
|  | static int eeprom_sim_init(const struct device *dev) | 
|  | { | 
|  | SYNC_INIT(); | 
|  | STATS_INIT_AND_REG(eeprom_sim_stats, STATS_SIZE_32, "eeprom_sim_stats"); | 
|  | STATS_INIT_AND_REG(eeprom_sim_thresholds, STATS_SIZE_32, | 
|  | "eeprom_sim_thresholds"); | 
|  |  | 
|  | return eeprom_mock_init(dev); | 
|  | } | 
|  |  | 
|  | DEVICE_DT_INST_DEFINE(0, &eeprom_sim_init, NULL, NULL, &eeprom_sim_config_0, POST_KERNEL, | 
|  | CONFIG_EEPROM_INIT_PRIORITY, &eeprom_sim_api); | 
|  |  | 
|  | #ifdef CONFIG_ARCH_POSIX | 
|  |  | 
|  | static void eeprom_native_cleanup(void) | 
|  | { | 
|  | eeprom_mock_cleanup_native(eeprom_in_ram, eeprom_fd, mock_eeprom, DT_INST_PROP(0, size), | 
|  | eeprom_file_path, eeprom_rm_at_exit); | 
|  | } | 
|  |  | 
|  | static void eeprom_native_options(void) | 
|  | { | 
|  | static struct args_struct_t eeprom_options[] = { | 
|  | {.option = "eeprom", | 
|  | .name = "path", | 
|  | .type = 's', | 
|  | .dest = (void *)&eeprom_file_path, | 
|  | .descript = "Path to binary file to be used as EEPROM, by default " | 
|  | "\"" DEFAULT_EEPROM_FILE_PATH "\""}, | 
|  | {.is_switch = true, | 
|  | .option = "eeprom_erase", | 
|  | .type = 'b', | 
|  | .dest = (void *)&eeprom_erase_at_start, | 
|  | .descript = "Erase the EEPROM content at startup"}, | 
|  | {.is_switch = true, | 
|  | .option = "eeprom_rm", | 
|  | .type = 'b', | 
|  | .dest = (void *)&eeprom_rm_at_exit, | 
|  | .descript = "Remove the EEPROM file when terminating the execution"}, | 
|  | {.is_switch = true, | 
|  | .option = "eeprom_in_ram", | 
|  | .type = 'b', | 
|  | .dest = (void *)&eeprom_in_ram, | 
|  | .descript = "Instead of a file, keep the file content just in RAM. If this is " | 
|  | "set, eeprom, eeprom_erase & eeprom_rm are ignored, and the EEPROM " | 
|  | "content is always erased at startup"}, | 
|  | ARG_TABLE_ENDMARKER | 
|  | }; | 
|  |  | 
|  | native_add_command_line_opts(eeprom_options); | 
|  | } | 
|  |  | 
|  | NATIVE_TASK(eeprom_native_options, PRE_BOOT_1, 1); | 
|  | NATIVE_TASK(eeprom_native_cleanup, ON_EXIT, 1); | 
|  |  | 
|  | #endif /* CONFIG_ARCH_POSIX */ |