/src/barebox/common/resource.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-only |
2 | | /* |
3 | | * resource.c - barebox resource management |
4 | | * |
5 | | * Copyright (c) 2011 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix |
6 | | */ |
7 | | #include <common.h> |
8 | | #include <malloc.h> |
9 | | #include <errno.h> |
10 | | #include <init.h> |
11 | | #include <linux/ioport.h> |
12 | | #include <linux/err.h> |
13 | | #include <linux/resource_ext.h> |
14 | | #include <asm/io.h> |
15 | | |
16 | | #ifdef CONFIG_DEBUG_RESOURCES |
17 | | #define res_dbg(args...) pr_info(args) |
18 | | #else |
19 | 0 | #define res_dbg(args...) pr_debug(args) |
20 | | #endif |
21 | | |
22 | | static int init_resource(struct resource *res, const char *name) |
23 | 0 | { |
24 | 0 | INIT_LIST_HEAD(&res->children); |
25 | 0 | res->parent = NULL; |
26 | 0 | res->name = xstrdup_const(name); |
27 | |
|
28 | 0 | return 0; |
29 | 0 | } |
30 | | |
31 | | /* |
32 | | * request a region. |
33 | | * This will succeed when the requested region is completely inside |
34 | | * the parent resource and does not conflict with any of the child |
35 | | * resources. |
36 | | */ |
37 | | struct resource *__request_region(struct resource *parent, |
38 | | resource_size_t start, resource_size_t end, |
39 | | const char *name, unsigned flags) |
40 | 0 | { |
41 | 0 | struct resource *r, *new; |
42 | |
|
43 | 0 | if (end < start) { |
44 | 0 | res_dbg("request region(0x%08llx:0x%08llx): end < start\n", |
45 | 0 | (unsigned long long)start, |
46 | 0 | (unsigned long long)end); |
47 | 0 | return ERR_PTR(-EINVAL); |
48 | 0 | } |
49 | | |
50 | | /* outside parent resource? */ |
51 | 0 | if (start < parent->start || end > parent->end) { |
52 | 0 | res_dbg("request region(0x%08llx:0x%08llx): outside parent resource 0x%08llx:0x%08llx\n", |
53 | 0 | (unsigned long long)start, |
54 | 0 | (unsigned long long)end, |
55 | 0 | (unsigned long long)parent->start, |
56 | 0 | (unsigned long long)parent->end); |
57 | 0 | return ERR_PTR(-EINVAL); |
58 | 0 | } |
59 | | |
60 | | /* |
61 | | * We keep the list of child resources ordered which helps |
62 | | * us searching for conflicts here. |
63 | | */ |
64 | 0 | list_for_each_entry(r, &parent->children, sibling) { |
65 | 0 | if (end < r->start) |
66 | 0 | goto ok; |
67 | 0 | if (start > r->end) |
68 | 0 | continue; |
69 | 0 | res_dbg("request region(0x%08llx:0x%08llx): %s conflicts with 0x%08llx:0x%08llx (%s)\n", |
70 | 0 | (unsigned long long)start, |
71 | 0 | (unsigned long long)end, |
72 | 0 | name, |
73 | 0 | (unsigned long long)r->start, |
74 | 0 | (unsigned long long)r->end, |
75 | 0 | r->name); |
76 | 0 | return ERR_PTR(-EBUSY); |
77 | 0 | } |
78 | | |
79 | 0 | ok: |
80 | 0 | res_dbg("request region(0x%08llx:0x%08llx): success flags=0x%x\n", |
81 | 0 | (unsigned long long)start, |
82 | 0 | (unsigned long long)end, flags); |
83 | |
|
84 | 0 | new = xzalloc(sizeof(*new)); |
85 | 0 | init_resource(new, name); |
86 | 0 | new->start = start; |
87 | 0 | new->end = end; |
88 | 0 | new->parent = parent; |
89 | 0 | new->flags = flags; |
90 | 0 | list_add_tail(&new->sibling, &r->sibling); |
91 | |
|
92 | 0 | return new; |
93 | 0 | } |
94 | | |
95 | | /** |
96 | | * resize_region - change the size of an allocated resource region |
97 | | * @res: resource to resize (may be NULL for no-op) |
98 | | * @size: new desired size in bytes (must be non-zero) |
99 | | * |
100 | | * Shrinking checks that no child extends past the new end. |
101 | | * Growing checks that the new end does not overflow, stays within |
102 | | * the parent region and does not collide with the next sibling. |
103 | | * |
104 | | * Return: 0 on success, negative error code on failure |
105 | | */ |
106 | | int resize_region(struct resource *res, resource_size_t size) |
107 | 0 | { |
108 | 0 | struct resource *parent; |
109 | 0 | struct resource *next; |
110 | 0 | resource_size_t newend; |
111 | |
|
112 | 0 | if (!res) |
113 | 0 | return 0; |
114 | 0 | if (!size) |
115 | 0 | return -EINVAL; |
116 | | |
117 | 0 | if (size == resource_size(res)) |
118 | 0 | return 0; |
119 | | |
120 | 0 | if (size < resource_size(res)) { |
121 | 0 | struct resource *last; |
122 | |
|
123 | 0 | last = list_last_entry_or_null(&res->children, |
124 | 0 | struct resource, sibling); |
125 | 0 | if (last && last->end > res->start + size - 1) |
126 | 0 | return -EBUSY; |
127 | 0 | res->end = res->start + size - 1; |
128 | 0 | return 0; |
129 | 0 | } |
130 | | |
131 | 0 | parent = res->parent; |
132 | 0 | if (!parent) |
133 | 0 | return -EINVAL; |
134 | | |
135 | 0 | if (check_add_overflow(res->start, size - 1, &newend)) |
136 | 0 | return -EINVAL; |
137 | | |
138 | 0 | if (newend > parent->end) |
139 | 0 | return -EINVAL; |
140 | | |
141 | | /* |
142 | | * parent->children is the list_head that anchors the ordered list of |
143 | | * children. If res is not the last entry, the immediate next entry is |
144 | | * the only sibling we must check. |
145 | | */ |
146 | 0 | if (res->sibling.next != &parent->children) { |
147 | 0 | next = list_next_entry(res, sibling); |
148 | |
|
149 | 0 | if (newend >= next->start) |
150 | 0 | return -EBUSY; |
151 | 0 | } |
152 | | |
153 | 0 | res->end = newend; |
154 | 0 | return 0; |
155 | 0 | } |
156 | | |
157 | | /* |
158 | | * release a region previously requested with request_*_region |
159 | | */ |
160 | | int release_region(struct resource *res) |
161 | 0 | { |
162 | 0 | if (!res) |
163 | 0 | return 0; |
164 | 0 | if (!list_empty(&res->children)) |
165 | 0 | return -EBUSY; |
166 | | |
167 | 0 | list_del(&res->sibling); |
168 | 0 | free_const(res->name); |
169 | 0 | free(res); |
170 | |
|
171 | 0 | return 0; |
172 | 0 | } |
173 | | |
174 | | static int yes_free(struct resource *res, void *data) |
175 | 0 | { |
176 | 0 | return 1; |
177 | 0 | } |
178 | | |
179 | | /* |
180 | | * release a region previously requested with request_*_region |
181 | | */ |
182 | | int release_region_range(struct resource *parent, |
183 | | resource_size_t start, resource_size_t size, |
184 | | int (*should_free)(struct resource *res, void *data), |
185 | | void *data) |
186 | 0 | { |
187 | 0 | resource_size_t end = start + size - 1; |
188 | 0 | struct resource *r, *tmp; |
189 | 0 | int ret, err = 0; |
190 | |
|
191 | 0 | if (end < parent->start || start > parent->end) |
192 | 0 | return 0; |
193 | | |
194 | 0 | if (!should_free) |
195 | 0 | should_free = yes_free; |
196 | |
|
197 | 0 | list_for_each_entry_safe(r, tmp, &parent->children, sibling) { |
198 | 0 | if (end < r->start || start > r->end) |
199 | 0 | continue; |
200 | | |
201 | | /* |
202 | | * CASE 1: fully covered |
203 | | * |
204 | | * r: |----------------| |
205 | | * cut: |xxxxxxxxxxxxxxxxxxx| |
206 | | * |
207 | | * remove fully |
208 | | */ |
209 | 0 | if (start <= r->start && r->end <= end) { |
210 | 0 | ret = should_free(r, data); |
211 | 0 | if (ret < 0) |
212 | 0 | return ret; |
213 | 0 | if (ret == 0) |
214 | 0 | continue; |
215 | | |
216 | 0 | ret = release_region(r); |
217 | 0 | if (ret) |
218 | 0 | err = ret; |
219 | 0 | continue; |
220 | 0 | } |
221 | | |
222 | | /* |
223 | | * CASE 2: trim head |
224 | | * |
225 | | * r: |----------------| |
226 | | * cut: |xxxxx| |
227 | | * new pieces: |
228 | | * left = removed |
229 | | * right = end+1 .. r.end |
230 | | */ |
231 | 0 | if (start <= r->start && r->end > end) { |
232 | 0 | ret = should_free(r, data); |
233 | 0 | if (ret < 0) |
234 | 0 | return ret; |
235 | 0 | if (ret == 0) |
236 | 0 | continue; |
237 | | |
238 | 0 | if (list_empty(&r->children)) |
239 | 0 | r->start = end + 1; |
240 | 0 | else |
241 | 0 | err = -EBUSY; |
242 | 0 | continue; |
243 | 0 | } |
244 | | |
245 | | /* |
246 | | * CASE 3: trim tail |
247 | | * |
248 | | * r: |----------------| |
249 | | * cut: |xxxxx| |
250 | | * new pieces: |
251 | | * left = r.start .. start-1 |
252 | | * right = removed |
253 | | */ |
254 | 0 | if (start > r->start && r->end <= end) { |
255 | 0 | ret = should_free(r, data); |
256 | 0 | if (ret < 0) |
257 | 0 | return ret; |
258 | 0 | if (ret == 0) |
259 | 0 | continue; |
260 | | |
261 | 0 | if (list_empty(&r->children)) |
262 | 0 | r->end = start - 1; |
263 | 0 | else |
264 | 0 | err = -EBUSY; |
265 | 0 | continue; |
266 | 0 | } |
267 | | |
268 | | /* |
269 | | * CASE 4: split |
270 | | * |
271 | | * r: |----------------| |
272 | | * cut: |xxxxx| |
273 | | * new pieces: |
274 | | * left = r.start .. start-1 |
275 | | * right = end+1 .. r.end |
276 | | */ |
277 | 0 | if (start > r->start && r->end > end) { |
278 | 0 | struct resource *right; |
279 | |
|
280 | 0 | ret = should_free(r, data); |
281 | 0 | if (ret < 0) |
282 | 0 | return ret; |
283 | 0 | if (ret == 0) |
284 | 0 | continue; |
285 | | |
286 | 0 | if (!list_empty(&r->children)) { |
287 | 0 | err = -EBUSY; |
288 | 0 | continue; |
289 | 0 | } |
290 | | |
291 | 0 | right = xzalloc(sizeof(*right)); |
292 | 0 | init_resource(right, r->name); |
293 | |
|
294 | 0 | right->start = end + 1; |
295 | 0 | right->end = r->end; |
296 | 0 | right->parent = parent; |
297 | 0 | right->flags = r->flags; |
298 | |
|
299 | 0 | r->end = start - 1; |
300 | |
|
301 | 0 | list_add(&right->sibling, &r->sibling); |
302 | 0 | continue; |
303 | 0 | } |
304 | 0 | } |
305 | | |
306 | 0 | return WARN_ON(err); |
307 | 0 | } |
308 | | |
309 | | /* |
310 | | * merge two adjacent sibling regions. |
311 | | */ |
312 | | int __merge_regions(const char *name, |
313 | | struct resource *resa, struct resource *resb) |
314 | 0 | { |
315 | 0 | if (!resource_adjacent(resa, resb)) |
316 | 0 | return -EINVAL; |
317 | | |
318 | 0 | if (resa->start < resb->start) |
319 | 0 | resa->end = resb->end; |
320 | 0 | else |
321 | 0 | resa->start = resb->start; |
322 | |
|
323 | 0 | free_const(resa->name); |
324 | 0 | resa->name = xstrdup_const(name); |
325 | 0 | release_region(resb); |
326 | |
|
327 | 0 | return 0; |
328 | 0 | } |
329 | | |
330 | | /* The root resource for the whole memory-mapped io space */ |
331 | | struct resource iomem_resource = { |
332 | | .start = 0, |
333 | | .end = ~(resource_size_t)0, |
334 | | .name = "iomem", |
335 | | .children = LIST_HEAD_INIT(iomem_resource.children), |
336 | | }; |
337 | | |
338 | | /* |
339 | | * request a region inside the io space (memory) |
340 | | */ |
341 | | struct resource *request_iomem_region(const char *name, |
342 | | resource_size_t start, resource_size_t end) |
343 | 0 | { |
344 | 0 | return __request_region(&iomem_resource, start, end, name, 0); |
345 | 0 | } |
346 | | |
347 | | /* The root resource for the whole io-mapped io space */ |
348 | | struct resource ioport_resource = { |
349 | | .start = 0, |
350 | | .end = IO_SPACE_LIMIT, |
351 | | .name = "ioport", |
352 | | .children = LIST_HEAD_INIT(ioport_resource.children), |
353 | | }; |
354 | | |
355 | | /* |
356 | | * request a region inside the io space (i/o port) |
357 | | */ |
358 | | struct resource *request_ioport_region(const char *name, |
359 | | resource_size_t start, resource_size_t end) |
360 | 0 | { |
361 | 0 | struct resource *res; |
362 | |
|
363 | 0 | res = __request_region(&ioport_resource, start, end, name, 0); |
364 | 0 | if (IS_ERR(res)) |
365 | 0 | return ERR_CAST(res); |
366 | | |
367 | 0 | return res; |
368 | 0 | } |
369 | | |
370 | | static struct resource * |
371 | | resource_iter_gap(struct resource *parent, struct resource *before, |
372 | | struct resource *gap, struct resource *after) |
373 | 0 | { |
374 | 0 | *gap = (struct resource) {}; |
375 | |
|
376 | 0 | gap->attrs = parent->attrs; |
377 | 0 | gap->type = parent->type; |
378 | 0 | gap->flags = IORESOURCE_UNSET | parent->flags; |
379 | 0 | gap->parent = parent; |
380 | 0 | INIT_LIST_HEAD(&gap->children); |
381 | |
|
382 | 0 | if (before) { |
383 | 0 | gap->start = before->end + 1; |
384 | 0 | gap->sibling.prev = &before->sibling; |
385 | 0 | } else { |
386 | 0 | gap->start = parent->start; |
387 | 0 | gap->sibling.prev = &parent->children; |
388 | 0 | } |
389 | |
|
390 | 0 | if (after) { |
391 | 0 | gap->end = after->start - 1; |
392 | 0 | gap->sibling.next = &after->sibling; |
393 | 0 | } else { |
394 | 0 | gap->end = parent->end; |
395 | 0 | gap->sibling.next = &parent->children; |
396 | 0 | } |
397 | |
|
398 | 0 | return gap; |
399 | 0 | } |
400 | | |
401 | | struct resource * |
402 | | resource_iter_first(struct resource *parent, struct resource *gap) |
403 | 0 | { |
404 | 0 | struct resource *child; |
405 | |
|
406 | 0 | if (!parent || region_is_gap(parent)) |
407 | 0 | return NULL; |
408 | | |
409 | 0 | child = list_first_entry_or_null(&parent->children, |
410 | 0 | struct resource, sibling); |
411 | 0 | if (!gap || (child && parent->start == child->start)) |
412 | 0 | return child; |
413 | | |
414 | 0 | return resource_iter_gap(parent, NULL, gap, child); |
415 | 0 | } |
416 | | |
417 | | struct resource * |
418 | | resource_iter_last(struct resource *parent, struct resource *gap) |
419 | 0 | { |
420 | 0 | struct resource *child; |
421 | |
|
422 | 0 | if (!parent || region_is_gap(parent)) |
423 | 0 | return NULL; |
424 | | |
425 | 0 | child = list_last_entry_or_null(&parent->children, |
426 | 0 | struct resource, sibling); |
427 | 0 | if (!gap || (child && parent->end == child->end)) |
428 | 0 | return child; |
429 | | |
430 | 0 | return resource_iter_gap(parent, child, gap, NULL); |
431 | 0 | } |
432 | | |
433 | | struct resource * |
434 | | resource_iter_next(struct resource *current, struct resource *gap) |
435 | 0 | { |
436 | 0 | struct resource *parent, *cursor, *next = NULL; |
437 | |
|
438 | 0 | if (!current || !current->parent) |
439 | 0 | return NULL; |
440 | | |
441 | 0 | parent = current->parent; |
442 | 0 | if (current->end == parent->end) |
443 | 0 | return NULL; |
444 | | |
445 | 0 | cursor = current; |
446 | 0 | list_for_each_entry_continue(cursor, &parent->children, sibling) { |
447 | 0 | next = cursor; |
448 | 0 | break; |
449 | 0 | } |
450 | |
|
451 | 0 | if (!gap || (next && resource_adjacent(current, next))) |
452 | 0 | return next; |
453 | | |
454 | 0 | return resource_iter_gap(parent, current, gap, next); |
455 | 0 | } |
456 | | |
457 | | struct resource * |
458 | | resource_iter_prev(struct resource *current, |
459 | | struct resource *gap) |
460 | 0 | { |
461 | 0 | struct resource *parent, *cursor, *prev = NULL; |
462 | |
|
463 | 0 | if (!current || !current->parent) |
464 | 0 | return NULL; |
465 | | |
466 | 0 | parent = current->parent; |
467 | 0 | if (current->start == parent->start) |
468 | 0 | return NULL; |
469 | | |
470 | 0 | cursor = current; |
471 | 0 | list_for_each_entry_continue_reverse(cursor, &parent->children, sibling) { |
472 | 0 | prev = cursor; |
473 | 0 | break; |
474 | 0 | } |
475 | |
|
476 | 0 | if (!gap || (prev && resource_adjacent(prev, current))) |
477 | 0 | return prev; |
478 | | |
479 | 0 | return resource_iter_gap(parent, prev, gap, current); |
480 | 0 | } |
481 | | |
482 | | /** |
483 | | * lookup_region - find the region containing a given address |
484 | | * @parent: parent resource to search within |
485 | | * @addr: address to look up |
486 | | * @gap: if non-NULL and addr is in a gap, the gap info is copied here |
487 | | * |
488 | | * Searches the children of @parent (and gaps between them) for the region |
489 | | * containing @addr. |
490 | | * |
491 | | * Return: pointer to the resource containing @addr, or @gap if @addr is in |
492 | | * a gap and @gap is non-NULL, or NULL if @addr is not found or is |
493 | | * in a gap and @gap is NULL |
494 | | */ |
495 | | struct resource *lookup_region(struct resource *parent, |
496 | | resource_size_t addr, struct resource *gap) |
497 | 0 | { |
498 | 0 | for_each_resource_region(parent, region) { |
499 | 0 | if (addr < region->start || region->end < addr) |
500 | 0 | continue; |
501 | | |
502 | 0 | if (!region_is_gap(region)) |
503 | 0 | return region; |
504 | 0 | if (!gap) |
505 | 0 | return NULL; |
506 | | |
507 | 0 | *gap = *region; |
508 | 0 | INIT_LIST_HEAD(&gap->sibling); |
509 | 0 | return gap; |
510 | 0 | } |
511 | | |
512 | 0 | return NULL; |
513 | 0 | } |
514 | | |
515 | | struct resource_entry *resource_list_create_entry(struct resource *res, |
516 | | size_t extra_size) |
517 | 0 | { |
518 | 0 | struct resource_entry *entry; |
519 | |
|
520 | 0 | entry = kzalloc(sizeof(*entry) + extra_size, GFP_KERNEL); |
521 | 0 | if (entry) { |
522 | 0 | INIT_LIST_HEAD(&entry->node); |
523 | 0 | entry->res = res ? res : &entry->__res; |
524 | 0 | } |
525 | |
|
526 | 0 | return entry; |
527 | 0 | } |
528 | | EXPORT_SYMBOL(resource_list_create_entry); |
529 | | |
530 | | static const char memory_type_name[][13] = { |
531 | | "Reserved", |
532 | | "Loader Code", |
533 | | "Loader Data", |
534 | | "Boot Code", |
535 | | "Boot Data", |
536 | | "Runtime Code", |
537 | | "Runtime Data", |
538 | | "Conventional", |
539 | | "Unusable", |
540 | | "ACPI Reclaim", |
541 | | "ACPI Mem NVS", |
542 | | "MMIO", |
543 | | "MMIO Port", |
544 | | "PAL Code", |
545 | | "Persistent", |
546 | | "Unaccepted", |
547 | | }; |
548 | | |
549 | | const char *resource_typeattr_format(char *buf, size_t size, |
550 | | const struct resource *res) |
551 | 0 | { |
552 | 0 | char *pos; |
553 | 0 | int type_len; |
554 | 0 | u64 attr; |
555 | |
|
556 | 0 | if (!(res->flags & IORESOURCE_TYPE_VALID)) |
557 | 0 | return NULL; |
558 | | |
559 | 0 | pos = buf; |
560 | 0 | type_len = snprintf(pos, size, "[%-*s", |
561 | 0 | (int)(sizeof(memory_type_name[0]) - 1), |
562 | 0 | memory_type_name[res->type]); |
563 | 0 | if (type_len >= size) |
564 | 0 | return buf; |
565 | | |
566 | 0 | pos += type_len; |
567 | 0 | size -= type_len; |
568 | |
|
569 | 0 | attr = res->attrs; |
570 | 0 | if (attr & ~(MEMATTR_UC | MEMATTR_WC | MEMATTR_WT | |
571 | 0 | MEMATTR_WB | MEMATTR_UCE | MEMATTR_RO | |
572 | 0 | MEMATTR_WP | MEMATTR_RP | MEMATTR_XP | |
573 | 0 | MEMATTR_NV | MEMATTR_SP | MEMATTR_MORE_RELIABLE) |
574 | 0 | ) |
575 | 0 | snprintf(pos, size, "|attr=0x%08llx]", |
576 | 0 | (unsigned long long)attr); |
577 | 0 | else |
578 | 0 | snprintf(pos, size, |
579 | 0 | "|%3s|%2s|%2s|%2s|%2s|%2s|%2s|%2s|%3s|%2s|%2s|%2s|%2s]", |
580 | 0 | res->runtime ? "RUN" : "", |
581 | 0 | attr & MEMATTR_MORE_RELIABLE ? "MR" : "", |
582 | 0 | attr & MEMATTR_SP ? "SP" : "", |
583 | 0 | attr & MEMATTR_NV ? "NV" : "", |
584 | 0 | attr & MEMATTR_XP ? "XP" : "", |
585 | 0 | attr & MEMATTR_RP ? "RP" : "", |
586 | 0 | attr & MEMATTR_WP ? "WP" : "", |
587 | 0 | attr & MEMATTR_RO ? "RO" : "", |
588 | 0 | attr & MEMATTR_UCE ? "UCE" : "", |
589 | 0 | attr & MEMATTR_WB ? "WB" : "", |
590 | 0 | attr & MEMATTR_WT ? "WT" : "", |
591 | 0 | attr & MEMATTR_WC ? "WC" : "", |
592 | 0 | attr & MEMATTR_UC ? "UC" : ""); |
593 | 0 | return buf; |
594 | 0 | } |