1 /*
2  * Copyright (c) 2019 Laczen
3  * Copyright (c) 2018 Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #define DT_DRV_COMPAT zephyr_sim_eeprom
9 
10 #ifdef CONFIG_ARCH_POSIX
11 #include "eeprom_simulator_native.h"
12 #include "cmdline.h"
13 #include "soc.h"
14 #endif /* CONFIG_ARCH_POSIX */
15 
16 #include <zephyr/device.h>
17 #include <zephyr/drivers/eeprom.h>
18 
19 #include <zephyr/init.h>
20 #include <zephyr/kernel.h>
21 #include <zephyr/sys/util.h>
22 #include <zephyr/stats/stats.h>
23 #include <string.h>
24 #include <errno.h>
25 
26 #define LOG_LEVEL CONFIG_EEPROM_LOG_LEVEL
27 #include <zephyr/logging/log.h>
28 LOG_MODULE_REGISTER(eeprom_simulator);
29 
30 struct eeprom_sim_config {
31 	size_t size;
32 	bool readonly;
33 };
34 
35 #define EEPROM(addr) (mock_eeprom + (addr))
36 
37 #if defined(CONFIG_MULTITHREADING)
38 /* semaphore for locking flash resources (tickers) */
39 static struct k_sem sem_lock;
40 #define SYNC_INIT() k_sem_init(&sem_lock, 1, 1)
41 #define SYNC_LOCK() k_sem_take(&sem_lock, K_FOREVER)
42 #define SYNC_UNLOCK() k_sem_give(&sem_lock)
43 #else
44 #define SYNC_INIT()
45 #define SYNC_LOCK()
46 #define SYNC_UNLOCK()
47 #endif
48 
49 /* simulator statistics */
50 STATS_SECT_START(eeprom_sim_stats)
51 STATS_SECT_ENTRY32(bytes_read)		/* total bytes read */
52 STATS_SECT_ENTRY32(bytes_written)	/* total bytes written */
53 STATS_SECT_ENTRY32(eeprom_read_calls)	/* calls to eeprom_read() */
54 STATS_SECT_ENTRY32(eeprom_read_time_us) /* time spent in eeprom_read() */
55 STATS_SECT_ENTRY32(eeprom_write_calls)  /* calls to eeprom_write() */
56 STATS_SECT_ENTRY32(eeprom_write_time_us)/* time spent in eeprom_write() */
57 STATS_SECT_END;
58 
59 STATS_SECT_DECL(eeprom_sim_stats) eeprom_sim_stats;
60 STATS_NAME_START(eeprom_sim_stats)
61 STATS_NAME(eeprom_sim_stats, bytes_read)
62 STATS_NAME(eeprom_sim_stats, bytes_written)
63 STATS_NAME(eeprom_sim_stats, eeprom_read_calls)
64 STATS_NAME(eeprom_sim_stats, eeprom_read_time_us)
65 STATS_NAME(eeprom_sim_stats, eeprom_write_calls)
66 STATS_NAME(eeprom_sim_stats, eeprom_write_time_us)
67 STATS_NAME_END(eeprom_sim_stats);
68 
69 /* simulator dynamic thresholds */
70 STATS_SECT_START(eeprom_sim_thresholds)
71 STATS_SECT_ENTRY32(max_write_calls)
72 STATS_SECT_ENTRY32(max_len)
73 STATS_SECT_END;
74 
75 STATS_SECT_DECL(eeprom_sim_thresholds) eeprom_sim_thresholds;
76 STATS_NAME_START(eeprom_sim_thresholds)
77 STATS_NAME(eeprom_sim_thresholds, max_write_calls)
78 STATS_NAME(eeprom_sim_thresholds, max_len)
79 STATS_NAME_END(eeprom_sim_thresholds);
80 
81 #ifdef CONFIG_ARCH_POSIX
82 static char *mock_eeprom;
83 static int eeprom_fd = -1;
84 static const char *eeprom_file_path;
85 #define DEFAULT_EEPROM_FILE_PATH "eeprom.bin"
86 static bool eeprom_erase_at_start;
87 static bool eeprom_rm_at_exit;
88 static bool eeprom_in_ram;
89 #else
90 static uint8_t mock_eeprom[DT_INST_PROP(0, size)];
91 #endif /* CONFIG_ARCH_POSIX */
92 
eeprom_range_is_valid(const struct device * dev,off_t offset,size_t len)93 static int eeprom_range_is_valid(const struct device *dev, off_t offset,
94 				 size_t len)
95 {
96 	const struct eeprom_sim_config *config = dev->config;
97 
98 	if ((offset + len) <= config->size) {
99 		return 1;
100 	}
101 
102 	return 0;
103 }
104 
eeprom_sim_read(const struct device * dev,off_t offset,void * data,size_t len)105 static int eeprom_sim_read(const struct device *dev, off_t offset, void *data,
106 			   size_t len)
107 {
108 	if (!len) {
109 		return 0;
110 	}
111 
112 	if (!eeprom_range_is_valid(dev, offset, len)) {
113 		LOG_WRN("attempt to read past device boundary");
114 		return -EINVAL;
115 	}
116 
117 	SYNC_LOCK();
118 
119 	STATS_INC(eeprom_sim_stats, eeprom_read_calls);
120 	memcpy(data, EEPROM(offset), len);
121 	STATS_INCN(eeprom_sim_stats, bytes_read, len);
122 
123 	SYNC_UNLOCK();
124 
125 #ifdef CONFIG_EEPROM_SIMULATOR_SIMULATE_TIMING
126 	k_busy_wait(CONFIG_EEPROM_SIMULATOR_MIN_READ_TIME_US);
127 	STATS_INCN(eeprom_sim_stats, eeprom_read_time_us,
128 		   CONFIG_EEPROM_SIMULATOR_MIN_READ_TIME_US);
129 #endif
130 
131 	return 0;
132 }
133 
eeprom_sim_write(const struct device * dev,off_t offset,const void * data,size_t len)134 static int eeprom_sim_write(const struct device *dev, off_t offset,
135 			    const void *data,
136 			    size_t len)
137 {
138 	const struct eeprom_sim_config *config = dev->config;
139 
140 	if (config->readonly) {
141 		LOG_WRN("attempt to write to read-only device");
142 		return -EACCES;
143 	}
144 
145 	if (!len) {
146 		return 0;
147 	}
148 
149 	if (!eeprom_range_is_valid(dev, offset, len)) {
150 		LOG_WRN("attempt to write past device boundary");
151 		return -EINVAL;
152 	}
153 
154 	SYNC_LOCK();
155 
156 	STATS_INC(eeprom_sim_stats, eeprom_write_calls);
157 
158 	bool data_part_ignored = false;
159 
160 	if (eeprom_sim_thresholds.max_write_calls != 0) {
161 		if (eeprom_sim_stats.eeprom_write_calls >
162 			eeprom_sim_thresholds.max_write_calls) {
163 			goto end;
164 		} else if (eeprom_sim_stats.eeprom_write_calls ==
165 				eeprom_sim_thresholds.max_write_calls) {
166 			if (eeprom_sim_thresholds.max_len == 0) {
167 				goto end;
168 			}
169 
170 			data_part_ignored = true;
171 		}
172 	}
173 
174 	if ((data_part_ignored) && (len > eeprom_sim_thresholds.max_len)) {
175 		len = eeprom_sim_thresholds.max_len;
176 	}
177 
178 	memcpy(EEPROM(offset), data, len);
179 
180 	STATS_INCN(eeprom_sim_stats, bytes_written, len);
181 
182 #ifdef CONFIG_EEPROM_SIMULATOR_SIMULATE_TIMING
183 	/* wait before returning */
184 	k_busy_wait(CONFIG_EEPROM_SIMULATOR_MIN_WRITE_TIME_US);
185 	STATS_INCN(eeprom_sim_stats, eeprom_write_time_us,
186 		   CONFIG_EEPROM_SIMULATOR_MIN_WRITE_TIME_US);
187 #endif
188 
189 end:
190 	SYNC_UNLOCK();
191 	return 0;
192 }
193 
eeprom_sim_size(const struct device * dev)194 static size_t eeprom_sim_size(const struct device *dev)
195 {
196 	const struct eeprom_sim_config *config = dev->config;
197 
198 	return config->size;
199 }
200 
201 static DEVICE_API(eeprom, eeprom_sim_api) = {
202 	.read = eeprom_sim_read,
203 	.write = eeprom_sim_write,
204 	.size = eeprom_sim_size,
205 };
206 
207 static const struct eeprom_sim_config eeprom_sim_config_0 = {
208 	.size = DT_INST_PROP(0, size),
209 	.readonly = DT_INST_PROP(0, read_only),
210 };
211 
212 #ifdef CONFIG_ARCH_POSIX
213 
eeprom_mock_init(const struct device * dev)214 static int eeprom_mock_init(const struct device *dev)
215 {
216 	int rc;
217 
218 	ARG_UNUSED(dev);
219 
220 	if (eeprom_in_ram == false && eeprom_file_path == NULL) {
221 		eeprom_file_path = DEFAULT_EEPROM_FILE_PATH;
222 	}
223 
224 	rc = eeprom_mock_init_native(eeprom_in_ram, &mock_eeprom, DT_INST_PROP(0, size), &eeprom_fd,
225 				     eeprom_file_path, 0xFF, eeprom_erase_at_start);
226 
227 	if (rc < 0) {
228 		return -EIO;
229 	} else {
230 		return 0;
231 	}
232 }
233 
234 #else
235 
eeprom_mock_init(const struct device * dev)236 static int eeprom_mock_init(const struct device *dev)
237 {
238 	memset(mock_eeprom, 0xFF, ARRAY_SIZE(mock_eeprom));
239 	return 0;
240 }
241 
242 #endif /* CONFIG_ARCH_POSIX */
243 
eeprom_sim_init(const struct device * dev)244 static int eeprom_sim_init(const struct device *dev)
245 {
246 	SYNC_INIT();
247 	STATS_INIT_AND_REG(eeprom_sim_stats, STATS_SIZE_32, "eeprom_sim_stats");
248 	STATS_INIT_AND_REG(eeprom_sim_thresholds, STATS_SIZE_32,
249 			   "eeprom_sim_thresholds");
250 
251 	return eeprom_mock_init(dev);
252 }
253 
254 DEVICE_DT_INST_DEFINE(0, &eeprom_sim_init, NULL, NULL, &eeprom_sim_config_0, POST_KERNEL,
255 		      CONFIG_EEPROM_INIT_PRIORITY, &eeprom_sim_api);
256 
257 #ifdef CONFIG_ARCH_POSIX
258 
eeprom_native_cleanup(void)259 static void eeprom_native_cleanup(void)
260 {
261 	eeprom_mock_cleanup_native(eeprom_in_ram, eeprom_fd, mock_eeprom, DT_INST_PROP(0, size),
262 				   eeprom_file_path, eeprom_rm_at_exit);
263 }
264 
eeprom_native_options(void)265 static void eeprom_native_options(void)
266 {
267 	static struct args_struct_t eeprom_options[] = {
268 		{.option = "eeprom",
269 		 .name = "path",
270 		 .type = 's',
271 		 .dest = (void *)&eeprom_file_path,
272 		 .descript = "Path to binary file to be used as EEPROM, by default "
273 			     "\"" DEFAULT_EEPROM_FILE_PATH "\""},
274 		{.is_switch = true,
275 		 .option = "eeprom_erase",
276 		 .type = 'b',
277 		 .dest = (void *)&eeprom_erase_at_start,
278 		 .descript = "Erase the EEPROM content at startup"},
279 		{.is_switch = true,
280 		 .option = "eeprom_rm",
281 		 .type = 'b',
282 		 .dest = (void *)&eeprom_rm_at_exit,
283 		 .descript = "Remove the EEPROM file when terminating the execution"},
284 		{.is_switch = true,
285 		 .option = "eeprom_in_ram",
286 		 .type = 'b',
287 		 .dest = (void *)&eeprom_in_ram,
288 		 .descript = "Instead of a file, keep the file content just in RAM. If this is "
289 			     "set, eeprom, eeprom_erase & eeprom_rm are ignored, and the EEPROM "
290 			     "content is always erased at startup"},
291 		ARG_TABLE_ENDMARKER
292 	};
293 
294 	native_add_command_line_opts(eeprom_options);
295 }
296 
297 NATIVE_TASK(eeprom_native_options, PRE_BOOT_1, 1);
298 NATIVE_TASK(eeprom_native_cleanup, ON_EXIT, 1);
299 
300 #endif /* CONFIG_ARCH_POSIX */
301