blob: 2e8d0ca66505ab43d17fb828ad2d9ff28fca2769 [file] [log] [blame] [edit]
/*
* Copyright (c) 2019 Laczen
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT zephyr_sim_eeprom
#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>
#ifdef CONFIG_ARCH_POSIX
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include "cmdline.h"
#include "soc.h"
#endif
#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 */