You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
7.6 KiB
311 lines
7.6 KiB
/* |
|
* 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 |
|
#undef _POSIX_C_SOURCE |
|
/* Note: This is used only for interaction with the host C library, and is therefore exempt of |
|
* coding guidelines rule A.4&5 which applies to the embedded code using embedded libraries |
|
*/ |
|
#define _POSIX_C_SOURCE 200809L |
|
#include <unistd.h> |
|
#include <sys/types.h> |
|
#include <sys/mman.h> |
|
#include <fcntl.h> |
|
#include "cmdline.h" |
|
#include "soc.h" |
|
#endif |
|
|
|
#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 uint8_t *mock_eeprom; |
|
static int eeprom_fd = -1; |
|
static const char *eeprom_file_path; |
|
static const char default_eeprom_file_path[] = "eeprom.bin"; |
|
#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 const struct eeprom_driver_api 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) |
|
{ |
|
if (eeprom_file_path == NULL) { |
|
eeprom_file_path = default_eeprom_file_path; |
|
} |
|
|
|
eeprom_fd = open(eeprom_file_path, O_RDWR | O_CREAT, (mode_t)0600); |
|
if (eeprom_fd == -1) { |
|
posix_print_warning("Failed to open eeprom device file " |
|
"%s: %s\n", |
|
eeprom_file_path, strerror(errno)); |
|
return -EIO; |
|
} |
|
|
|
if (ftruncate(eeprom_fd, DT_INST_PROP(0, size)) == -1) { |
|
posix_print_warning("Failed to resize eeprom device file " |
|
"%s: %s\n", |
|
eeprom_file_path, strerror(errno)); |
|
return -EIO; |
|
} |
|
|
|
mock_eeprom = mmap(NULL, DT_INST_PROP(0, size), |
|
PROT_WRITE | PROT_READ, MAP_SHARED, eeprom_fd, 0); |
|
if (mock_eeprom == MAP_FAILED) { |
|
posix_print_warning("Failed to mmap eeprom device file " |
|
"%s: %s\n", |
|
eeprom_file_path, strerror(errno)); |
|
return -EIO; |
|
} |
|
|
|
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_posix_cleanup(void) |
|
{ |
|
if ((mock_eeprom != MAP_FAILED) && (mock_eeprom != NULL)) { |
|
munmap(mock_eeprom, DT_INST_PROP(0, size)); |
|
} |
|
|
|
if (eeprom_fd != -1) { |
|
close(eeprom_fd); |
|
} |
|
} |
|
|
|
static void eeprom_native_posix_options(void) |
|
{ |
|
static struct args_struct_t eeprom_options[] = { |
|
{ .manual = false, |
|
.is_mandatory = false, |
|
.is_switch = false, |
|
.option = "eeprom", |
|
.name = "path", |
|
.type = 's', |
|
.dest = (void *)&eeprom_file_path, |
|
.call_when_found = NULL, |
|
.descript = "Path to binary file to be used as eeprom" }, |
|
ARG_TABLE_ENDMARKER |
|
}; |
|
|
|
native_add_command_line_opts(eeprom_options); |
|
} |
|
|
|
|
|
NATIVE_TASK(eeprom_native_posix_options, PRE_BOOT_1, 1); |
|
NATIVE_TASK(eeprom_native_posix_cleanup, ON_EXIT, 1); |
|
|
|
#endif /* CONFIG_ARCH_POSIX */
|
|
|