1 /*
2 * Copyright (c) 2019-2026, Arm Limited and Contributors. All rights reserved.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #include <assert.h>
8 #include <errno.h>
9 #include <limits.h>
10 #include <string.h>
11
12 #include <common/debug.h>
13 #include <drivers/io/io_driver.h>
14 #include <drivers/io/io_mtd.h>
15 #include <lib/utils.h>
16
17 #include <platform_def.h>
18
19 typedef struct {
20 io_mtd_dev_spec_t *dev_spec;
21 uintptr_t base;
22 unsigned long long pos; /* Offset in bytes */
23 unsigned long long size; /* Size of device in bytes */
24 unsigned long long extra_offset; /* Extra offset in bytes */
25 } mtd_dev_state_t;
26
27 io_type_t device_type_mtd(void);
28
29 static int mtd_open(io_dev_info_t *dev_info, const uintptr_t spec,
30 io_entity_t *entity);
31 static int mtd_seek(io_entity_t *entity, int mode, signed long long offset);
32 static int mtd_len(io_entity_t *entity, size_t *length);
33 static int mtd_read(io_entity_t *entity, uintptr_t buffer, size_t length,
34 size_t *length_read);
35 static int mtd_close(io_entity_t *entity);
36 static int mtd_dev_open(const uintptr_t dev_spec, io_dev_info_t **dev_info);
37 static int mtd_dev_close(io_dev_info_t *dev_info);
38
39 static const io_dev_connector_t mtd_dev_connector = {
40 .dev_open = mtd_dev_open
41 };
42
43 static const io_dev_funcs_t mtd_dev_funcs = {
44 .type = device_type_mtd,
45 .open = mtd_open,
46 .seek = mtd_seek,
47 .size = mtd_len,
48 .read = mtd_read,
49 .close = mtd_close,
50 .dev_close = mtd_dev_close,
51 };
52
53 static mtd_dev_state_t state_pool[MAX_IO_MTD_DEVICES];
54 static io_dev_info_t dev_info_pool[MAX_IO_MTD_DEVICES];
55
device_type_mtd(void)56 io_type_t device_type_mtd(void)
57 {
58 return IO_TYPE_MTD;
59 }
60
61 /* Locate a MTD state in the pool, specified by address */
find_first_mtd_state(const io_mtd_dev_spec_t * dev_spec,unsigned int * index_out)62 static int find_first_mtd_state(const io_mtd_dev_spec_t *dev_spec,
63 unsigned int *index_out)
64 {
65 unsigned int index;
66 int result = -ENOENT;
67
68 for (index = 0U; index < MAX_IO_MTD_DEVICES; index++) {
69 /* dev_spec is used as identifier since it's unique */
70 if (state_pool[index].dev_spec == dev_spec) {
71 result = 0;
72 *index_out = index;
73 break;
74 }
75 }
76
77 return result;
78 }
79
80 /* Allocate a device info from the pool */
allocate_dev_info(io_dev_info_t ** dev_info)81 static int allocate_dev_info(io_dev_info_t **dev_info)
82 {
83 unsigned int index = 0U;
84 int result;
85
86 result = find_first_mtd_state(NULL, &index);
87 if (result != 0) {
88 return -ENOMEM;
89 }
90
91 dev_info_pool[index].funcs = &mtd_dev_funcs;
92 dev_info_pool[index].info = (uintptr_t)&state_pool[index];
93 *dev_info = &dev_info_pool[index];
94
95 return 0;
96 }
97
98 /* Release a device info from the pool */
free_dev_info(io_dev_info_t * dev_info)99 static int free_dev_info(io_dev_info_t *dev_info)
100 {
101 int result;
102 unsigned int index = 0U;
103 mtd_dev_state_t *state;
104
105 state = (mtd_dev_state_t *)dev_info->info;
106 result = find_first_mtd_state(state->dev_spec, &index);
107 if (result != 0) {
108 return result;
109 }
110
111 zeromem(state, sizeof(mtd_dev_state_t));
112 zeromem(dev_info, sizeof(io_dev_info_t));
113
114 return 0;
115 }
116
mtd_add_extra_offset(mtd_dev_state_t * cur,size_t * extra_offset)117 static int mtd_add_extra_offset(mtd_dev_state_t *cur, size_t *extra_offset)
118 {
119 io_mtd_ops_t *ops = &cur->dev_spec->ops;
120 int ret;
121
122 if (ops->seek == NULL) {
123 return 0;
124 }
125
126 ret = ops->seek(cur->base, cur->pos, extra_offset);
127 if (ret != 0) {
128 ERROR("%s: Seek error %d\n", __func__, ret);
129 return ret;
130 }
131
132 return 0;
133 }
134
mtd_open(io_dev_info_t * dev_info,const uintptr_t spec,io_entity_t * entity)135 static int mtd_open(io_dev_info_t *dev_info, const uintptr_t spec,
136 io_entity_t *entity)
137 {
138 mtd_dev_state_t *cur;
139 io_block_spec_t *region;
140 size_t extra_offset = 0U;
141 int ret;
142
143 assert((dev_info->info != 0UL) && (entity->info == 0UL));
144
145 region = (io_block_spec_t *)spec;
146 cur = (mtd_dev_state_t *)dev_info->info;
147 entity->info = (uintptr_t)cur;
148 cur->base = region->offset;
149 cur->pos = 0U;
150 cur->extra_offset = 0U;
151
152 ret = mtd_add_extra_offset(cur, &extra_offset);
153 if (ret != 0) {
154 return ret;
155 }
156
157 cur->base += extra_offset;
158
159 return 0;
160 }
161
162 /* Seek to a specific position using offset */
mtd_seek(io_entity_t * entity,int mode,signed long long offset)163 static int mtd_seek(io_entity_t *entity, int mode, signed long long offset)
164 {
165 mtd_dev_state_t *cur;
166 size_t extra_offset = 0U;
167 int ret;
168
169 assert((entity->info != (uintptr_t)NULL) && (offset >= 0));
170
171 cur = (mtd_dev_state_t *)entity->info;
172
173 switch (mode) {
174 case IO_SEEK_SET:
175 if ((offset >= 0) &&
176 ((unsigned long long)offset >= cur->size)) {
177 return -EINVAL;
178 }
179
180 cur->pos = offset;
181 break;
182 case IO_SEEK_CUR:
183 if (((cur->base + cur->pos + (unsigned long long)offset) >=
184 cur->size) ||
185 ((cur->base + cur->pos + (unsigned long long)offset) <
186 cur->base + cur->pos)) {
187 return -EINVAL;
188 }
189
190 cur->pos += (unsigned long long)offset;
191 break;
192 default:
193 return -EINVAL;
194 }
195
196 ret = mtd_add_extra_offset(cur, &extra_offset);
197 if (ret != 0) {
198 return ret;
199 }
200
201 cur->extra_offset = extra_offset;
202
203 return 0;
204 }
205
mtd_len(io_entity_t * entity,size_t * length)206 static int mtd_len(io_entity_t *entity, size_t *length)
207 {
208 mtd_dev_state_t *cur;
209
210 assert(entity->info != (uintptr_t)NULL);
211 assert(length != NULL);
212
213 cur = (mtd_dev_state_t *)entity->info;
214 if (cur->size > (unsigned long long)SIZE_MAX) {
215 return -EINVAL;
216 }
217
218 *length = (size_t)cur->size;
219 return 0;
220 }
221
mtd_read(io_entity_t * entity,uintptr_t buffer,size_t length,size_t * out_length)222 static int mtd_read(io_entity_t *entity, uintptr_t buffer, size_t length,
223 size_t *out_length)
224 {
225 mtd_dev_state_t *cur;
226 io_mtd_ops_t *ops;
227 int ret;
228
229 assert(entity->info != (uintptr_t)NULL);
230 assert((length > 0U) && (buffer != (uintptr_t)NULL));
231
232 cur = (mtd_dev_state_t *)entity->info;
233 ops = &cur->dev_spec->ops;
234 assert(ops->read != NULL);
235
236 VERBOSE("Read at %llx into %lx, length %zu\n",
237 cur->base + cur->pos, buffer, length);
238 if ((cur->base + cur->pos + length) > cur->dev_spec->device_size) {
239 return -EINVAL;
240 }
241
242 ret = ops->read(cur->base + cur->pos + cur->extra_offset, buffer,
243 length, out_length);
244 if (ret < 0) {
245 return ret;
246 }
247
248 assert(*out_length == length);
249 cur->pos += *out_length;
250
251 return 0;
252 }
253
mtd_close(io_entity_t * entity)254 static int mtd_close(io_entity_t *entity)
255 {
256 entity->info = (uintptr_t)NULL;
257
258 return 0;
259 }
260
mtd_dev_open(const uintptr_t dev_spec,io_dev_info_t ** dev_info)261 static int mtd_dev_open(const uintptr_t dev_spec, io_dev_info_t **dev_info)
262 {
263 mtd_dev_state_t *cur;
264 io_dev_info_t *info;
265 io_mtd_ops_t *ops;
266 int result;
267
268 result = allocate_dev_info(&info);
269 if (result != 0) {
270 return -ENOENT;
271 }
272
273 cur = (mtd_dev_state_t *)info->info;
274 cur->dev_spec = (io_mtd_dev_spec_t *)dev_spec;
275 *dev_info = info;
276 ops = &(cur->dev_spec->ops);
277 if (ops->init != NULL) {
278 result = ops->init(&cur->dev_spec->device_size,
279 &cur->dev_spec->erase_size);
280 }
281
282 if (result == 0) {
283 cur->size = cur->dev_spec->device_size;
284 } else {
285 cur->size = 0ULL;
286 }
287
288 return result;
289 }
290
mtd_dev_close(io_dev_info_t * dev_info)291 static int mtd_dev_close(io_dev_info_t *dev_info)
292 {
293 return free_dev_info(dev_info);
294 }
295
296 /* Exported functions */
297
298 /* Register the MTD driver in the IO abstraction */
register_io_dev_mtd(const io_dev_connector_t ** dev_con)299 int register_io_dev_mtd(const io_dev_connector_t **dev_con)
300 {
301 int result;
302
303 result = io_register_device(&dev_info_pool[0]);
304 if (result == 0) {
305 *dev_con = &mtd_dev_connector;
306 }
307
308 return result;
309 }
310