1 /* SPDX-License-Identifier: GPL-2.0-only */
2 /*
3 * Device tree overlay support in Xen.
4 *
5 * Copyright (C) 2023, Advanced Micro Devices, Inc. All Rights Reserved.
6 * Written by Vikram Garhwal <vikram.garhwal@amd.com>
7 *
8 */
9 #include <xen/dt-overlay.h>
10 #include <xen/fdt-kernel.h>
11 #include <xen/guest_access.h>
12 #include <xen/iocap.h>
13 #include <xen/libfdt/libfdt.h>
14 #include <xen/xmalloc.h>
15
16 #include <asm/setup.h>
17
18 #define DT_OVERLAY_MAX_SIZE KB(500)
19
20 static LIST_HEAD(overlay_tracker);
21 static DEFINE_SPINLOCK(overlay_lock);
22
23 /* Find last descendants of the device_node. */
24 static struct dt_device_node *
find_last_descendants_node(const struct dt_device_node * device_node)25 find_last_descendants_node(const struct dt_device_node *device_node)
26 {
27 struct dt_device_node *child_node;
28
29 for ( child_node = device_node->child; child_node->sibling != NULL;
30 child_node = child_node->sibling );
31
32 /* If last child_node also have children. */
33 if ( child_node->child )
34 child_node = find_last_descendants_node(child_node);
35
36 return child_node;
37 }
38
39 /*
40 * Returns next node to the input node. If node has children then return
41 * last descendant's next node.
42 */
43 static struct dt_device_node *
dt_find_next_node(struct dt_device_node * dt,const struct dt_device_node * node)44 dt_find_next_node(struct dt_device_node *dt, const struct dt_device_node *node)
45 {
46 struct dt_device_node *np;
47
48 dt_for_each_device_node(dt, np)
49 if ( np == node )
50 break;
51
52 if ( np->child )
53 np = find_last_descendants_node(np);
54
55 return np->allnext;
56 }
57
dt_overlay_remove_node(struct dt_device_node * device_node)58 static int dt_overlay_remove_node(struct dt_device_node *device_node)
59 {
60 struct dt_device_node *np;
61 struct dt_device_node *parent_node;
62 struct dt_device_node *last_descendant = device_node->child;
63
64 parent_node = device_node->parent;
65
66 /* Check if we are trying to remove "/" i.e. root node. */
67 if ( parent_node == NULL )
68 {
69 dt_dprintk("%s's parent node not found\n", device_node->name);
70 return -EFAULT;
71 }
72
73 /* Sanity check for linking between parent and child node. */
74 np = parent_node->child;
75 if ( np == NULL )
76 {
77 dt_dprintk("parent node %s's not found\n", parent_node->name);
78 return -EFAULT;
79 }
80
81 /* If node to be removed is only child node or first child. */
82 if ( !dt_node_cmp(np->full_name, device_node->full_name) )
83 {
84 parent_node->child = np->sibling;
85
86 /*
87 * Iterate over all child nodes of device_node. Given that we are
88 * removing a node, we need to remove all it's descendants too.
89 * Reason behind finding last_descendant:
90 * If device_node has multiple children, device_node->allnext will point
91 * to first_child and first_child->allnext will be a sibling. When the
92 * device_node and it's all children are removed, parent_node->allnext
93 * should point to node next to last children.
94 */
95 if ( last_descendant )
96 {
97 last_descendant = find_last_descendants_node(device_node);
98 parent_node->allnext = last_descendant->allnext;
99 }
100 else
101 parent_node->allnext = np->allnext;
102
103 return 0;
104 }
105
106 for ( np = parent_node->child; np->sibling != NULL; np = np->sibling )
107 {
108 if ( !dt_node_cmp(np->sibling->full_name, device_node->full_name) )
109 {
110 /* Found the node. Now we remove it. */
111 np->sibling = np->sibling->sibling;
112
113 if ( np->child )
114 np = find_last_descendants_node(np);
115
116 /*
117 * Iterate over all child nodes of device_node. Given that we are
118 * removing parent node, we need to remove all it's descendants too.
119 */
120 if ( last_descendant )
121 last_descendant = find_last_descendants_node(device_node);
122
123 if ( last_descendant )
124 np->allnext = last_descendant->allnext;
125 else
126 np->allnext = np->allnext->allnext;
127
128 break;
129 }
130 }
131
132 return 0;
133 }
134
dt_overlay_add_node(struct dt_device_node * device_node,const char * parent_node_path)135 static int dt_overlay_add_node(struct dt_device_node *device_node,
136 const char *parent_node_path)
137 {
138 struct dt_device_node *parent_node;
139 struct dt_device_node *next_node;
140
141 parent_node = dt_find_node_by_path(parent_node_path);
142
143 if ( parent_node == NULL )
144 {
145 dt_dprintk("Parent node %s not found. Overlay node will not be added\n",
146 parent_node_path);
147 return -EINVAL;
148 }
149
150 /* If parent has no child. */
151 if ( parent_node->child == NULL )
152 {
153 next_node = parent_node->allnext;
154 device_node->parent = parent_node;
155 parent_node->allnext = device_node;
156 parent_node->child = device_node;
157 }
158 else
159 {
160 struct dt_device_node *np;
161 /*
162 * If parent has at least one child node.
163 * Iterate to the last child node of parent.
164 */
165 for ( np = parent_node->child; np->sibling != NULL; np = np->sibling );
166
167 /* Iterate over all child nodes of np node. */
168 if ( np->child )
169 {
170 struct dt_device_node *np_last_descendant;
171
172 np_last_descendant = find_last_descendants_node(np);
173
174 next_node = np_last_descendant->allnext;
175 np_last_descendant->allnext = device_node;
176 }
177 else
178 {
179 next_node = np->allnext;
180 np->allnext = device_node;
181 }
182
183 device_node->parent = parent_node;
184 np->sibling = device_node;
185 np->sibling->sibling = NULL;
186 }
187
188 /* Iterate over all child nodes of device_node to add children too. */
189 if ( device_node->child )
190 {
191 struct dt_device_node *device_node_last_descendant;
192
193 device_node_last_descendant = find_last_descendants_node(device_node);
194
195 /* Plug next_node at the end of last children of device_node. */
196 device_node_last_descendant->allnext = next_node;
197 }
198 else
199 {
200 /* Now plug next_node at the end of device_node. */
201 device_node->allnext = next_node;
202 }
203
204 return 0;
205 }
206
207 /* Basic sanity check for the dtbo tool stack provided to Xen. */
check_overlay_fdt(const void * overlay_fdt,uint32_t overlay_fdt_size)208 static int check_overlay_fdt(const void *overlay_fdt, uint32_t overlay_fdt_size)
209 {
210 if ( (fdt_totalsize(overlay_fdt) != overlay_fdt_size) ||
211 fdt_check_header(overlay_fdt) )
212 {
213 printk(XENLOG_ERR "The overlay FDT is not a valid Flat Device Tree\n");
214 return -EINVAL;
215 }
216
217 return 0;
218 }
219
irq_remove_cb(unsigned long s,unsigned long e,void * dom,unsigned long * c)220 static int irq_remove_cb(unsigned long s, unsigned long e, void *dom,
221 unsigned long *c)
222 {
223 int rc;
224 struct domain *d = dom;
225
226 /*
227 * TODO: We don't handle shared IRQs for now. So, it is assumed that
228 * the IRQs was not shared with another devices.
229 * TODO: Undo the IRQ routing.
230 */
231 rc = irq_deny_access(d, s);
232 if ( rc )
233 {
234 printk(XENLOG_ERR "unable to revoke access for irq %lu\n", s);
235 }
236 else
237 *c += e - s + 1;
238
239 return rc;
240
241 }
242
iomem_remove_cb(unsigned long s,unsigned long e,void * dom,unsigned long * c)243 static int iomem_remove_cb(unsigned long s, unsigned long e, void *dom,
244 unsigned long *c)
245 {
246 int rc;
247 struct domain *d = dom;
248
249 /*
250 * Remove mmio access.
251 * TODO: Support for remove/add the mapping in P2M.
252 */
253 rc = iomem_deny_access(d, s, e);
254 if ( rc )
255 {
256 printk(XENLOG_ERR "Unable to remove %pd access to %#lx - %#lx\n",
257 d, s, e);
258 }
259 else
260 *c += e - s + 1;
261
262 return rc;
263 }
264
265 /* Count number of nodes till one level of __overlay__ tag. */
overlay_node_count(const void * overlay_fdt)266 static unsigned int overlay_node_count(const void *overlay_fdt)
267 {
268 unsigned int num_overlay_nodes = 0;
269 int fragment;
270
271 fdt_for_each_subnode(fragment, overlay_fdt, 0)
272 {
273 int subnode;
274 int overlay;
275
276 overlay = fdt_subnode_offset(overlay_fdt, fragment, "__overlay__");
277 if ( overlay < 0 )
278 continue;
279
280 fdt_for_each_subnode(subnode, overlay_fdt, overlay)
281 {
282 num_overlay_nodes++;
283 }
284 }
285
286 return num_overlay_nodes;
287 }
288
289 /*
290 * overlay_get_nodes_info gets full name with path for all the nodes which
291 * are in one level of __overlay__ tag. This is useful when checking node for
292 * duplication i.e. dtbo tries to add nodes which already exists in device tree.
293 */
overlay_get_nodes_info(const void * fdto,char ** nodes_full_path)294 static int overlay_get_nodes_info(const void *fdto, char **nodes_full_path)
295 {
296 int fragment;
297 unsigned int node_num = 0;
298
299 fdt_for_each_subnode(fragment, fdto, 0)
300 {
301 int target;
302 int overlay;
303 int subnode;
304 const char *target_path;
305
306 overlay = fdt_subnode_offset(fdto, fragment, "__overlay__");
307 if ( overlay < 0 )
308 continue;
309
310 target = fdt_overlay_target_offset(device_tree_flattened, fdto,
311 fragment, &target_path);
312 if ( target < 0 )
313 return target;
314
315 if ( target_path == NULL )
316 return -EINVAL;
317
318 fdt_for_each_subnode(subnode, fdto, overlay)
319 {
320 const char *node_name = NULL;
321 int node_name_len;
322 unsigned int target_path_len = strlen(target_path);
323 unsigned int node_full_name_len;
324 unsigned int extra_len;
325
326 node_name = fdt_get_name(fdto, subnode, &node_name_len);
327
328 if ( node_name == NULL )
329 return node_name_len;
330
331 /*
332 * Extra length is for adding '/' and '\0' unless the target path is
333 * root in which case we don't add the '/' at the beginning. This is
334 * done to keep the node_full_path in the correct full node name
335 * format.
336 */
337 extra_len = (target_path_len > 1) ? 2 : 1;
338 node_full_name_len = target_path_len + node_name_len + extra_len;
339
340 nodes_full_path[node_num] = xmalloc_bytes(node_full_name_len);
341
342 if ( nodes_full_path[node_num] == NULL )
343 return -ENOMEM;
344
345 memcpy(nodes_full_path[node_num], target_path, target_path_len);
346
347 /* Target is not root - add separator */
348 if ( target_path_len > 1 )
349 nodes_full_path[node_num][target_path_len++] = '/';
350
351 memcpy(nodes_full_path[node_num] + target_path_len,
352 node_name, node_name_len);
353
354 nodes_full_path[node_num][node_full_name_len - 1] = '\0';
355
356 node_num++;
357 }
358 }
359
360 return 0;
361 }
362
363 /* This function should be called with the overlay_lock taken */
364 static struct overlay_track *
find_track_entry_from_tracker(const void * overlay_fdt,uint32_t overlay_fdt_size)365 find_track_entry_from_tracker(const void *overlay_fdt,
366 uint32_t overlay_fdt_size)
367 {
368 struct overlay_track *entry, *temp;
369 bool found_entry = false;
370
371 ASSERT(spin_is_locked(&overlay_lock));
372
373 /*
374 * First check if dtbo is correct i.e. it should one of the dtbo which was
375 * used when dynamically adding the node.
376 * Limitation: Cases with same node names but different property are not
377 * supported currently. We are relying on user to provide the same dtbo
378 * as it was used when adding the nodes.
379 */
380 list_for_each_entry_safe( entry, temp, &overlay_tracker, entry )
381 {
382 if ( memcmp(entry->overlay_fdt, overlay_fdt, overlay_fdt_size) == 0 )
383 {
384 found_entry = true;
385 break;
386 }
387 }
388
389 if ( !found_entry )
390 {
391 printk(XENLOG_ERR "Cannot find any matching tracker with input dtbo."
392 " Operation is supported only for prior added dtbo.\n");
393 return NULL;
394 }
395
396 return entry;
397 }
398
399 /* Check if node itself can be removed and remove node from IOMMU. */
remove_node_resources(struct dt_device_node * device_node)400 static int remove_node_resources(struct dt_device_node *device_node)
401 {
402 int rc = 0;
403 unsigned int len;
404 domid_t domid;
405
406 domid = dt_device_used_by(device_node);
407
408 dt_dprintk("Checking if node %s is used by any domain\n",
409 device_node->full_name);
410
411 /* Remove the node if only it's assigned to hardware domain or domain io. */
412 if ( domid != hardware_domain->domain_id && domid != DOMID_IO )
413 {
414 printk(XENLOG_ERR "Device %s is being used by domain %u. Removing nodes failed\n",
415 device_node->full_name, domid);
416 return -EINVAL;
417 }
418
419 /* Check if iommu property exists. */
420 if ( dt_get_property(device_node, "iommus", &len) )
421 {
422 if ( dt_device_is_protected(device_node) )
423 {
424 rc = iommu_remove_dt_device(device_node);
425 if ( rc < 0 )
426 return rc;
427 }
428 }
429
430 return rc;
431 }
432
433 /* Remove all descendants from IOMMU. */
434 static int
remove_descendant_nodes_resources(const struct dt_device_node * device_node)435 remove_descendant_nodes_resources(const struct dt_device_node *device_node)
436 {
437 int rc = 0;
438 struct dt_device_node *child_node;
439
440 for ( child_node = device_node->child; child_node != NULL;
441 child_node = child_node->sibling )
442 {
443 if ( child_node->child )
444 {
445 rc = remove_descendant_nodes_resources(child_node);
446 if ( rc )
447 return rc;
448 }
449
450 rc = remove_node_resources(child_node);
451 if ( rc )
452 return rc;
453 }
454
455 return rc;
456 }
457
458 /* Remove nodes from dt_host. */
remove_nodes(const struct overlay_track * tracker)459 static int remove_nodes(const struct overlay_track *tracker)
460 {
461 int rc = 0;
462 struct dt_device_node *overlay_node;
463 unsigned int j;
464 struct domain *d = hardware_domain;
465
466 for ( j = 0; j < tracker->num_nodes; j++ )
467 {
468 overlay_node = (struct dt_device_node *)tracker->nodes_address[j];
469 if ( overlay_node == NULL )
470 return -EINVAL;
471
472 write_lock(&dt_host_lock);
473
474 rc = remove_descendant_nodes_resources(overlay_node);
475 if ( rc )
476 {
477 write_unlock(&dt_host_lock);
478 return rc;
479 }
480
481 rc = remove_node_resources(overlay_node);
482 if ( rc )
483 {
484 write_unlock(&dt_host_lock);
485 return rc;
486 }
487
488 dt_dprintk("Removing node: %s\n", overlay_node->full_name);
489
490 rc = dt_overlay_remove_node(overlay_node);
491 if ( rc )
492 {
493 write_unlock(&dt_host_lock);
494 return rc;
495 }
496
497 write_unlock(&dt_host_lock);
498 }
499
500 /* Remove IRQ access. */
501 if ( tracker->irq_ranges )
502 {
503 rc = rangeset_consume_ranges(tracker->irq_ranges, irq_remove_cb, d);
504 if ( rc )
505 return rc;
506 }
507
508 /* Remove mmio access. */
509 if ( tracker->iomem_ranges )
510 {
511 rc = rangeset_consume_ranges(tracker->iomem_ranges, iomem_remove_cb, d);
512 if ( rc )
513 return rc;
514 }
515
516 return rc;
517 }
518
519 /*
520 * First finds the device node to remove. Check if the device is being used by
521 * any dom and finally remove it from dt_host. IOMMU is already being taken care
522 * while destroying the domain.
523 */
handle_remove_overlay_nodes(const void * overlay_fdt,uint32_t overlay_fdt_size)524 static long handle_remove_overlay_nodes(const void *overlay_fdt,
525 uint32_t overlay_fdt_size)
526 {
527 int rc;
528 struct overlay_track *entry;
529
530 rc = check_overlay_fdt(overlay_fdt, overlay_fdt_size);
531 if ( rc )
532 return rc;
533
534 spin_lock(&overlay_lock);
535
536 entry = find_track_entry_from_tracker(overlay_fdt, overlay_fdt_size);
537 if ( entry == NULL )
538 {
539 rc = -EINVAL;
540 goto out;
541
542 }
543
544 rc = remove_nodes(entry);
545 if ( rc )
546 {
547 printk(XENLOG_ERR "Removing node failed\n");
548 goto out;
549 }
550
551 list_del(&entry->entry);
552
553 xfree(entry->dt_host_new);
554 xfree(entry->fdt);
555 xfree(entry->overlay_fdt);
556
557 xfree(entry->nodes_address);
558
559 rangeset_destroy(entry->irq_ranges);
560 rangeset_destroy(entry->iomem_ranges);
561
562 xfree(entry);
563
564 out:
565 spin_unlock(&overlay_lock);
566 return rc;
567 }
568
free_nodes_full_path(unsigned int num_nodes,char ** nodes_full_path)569 static void free_nodes_full_path(unsigned int num_nodes, char **nodes_full_path)
570 {
571 unsigned int i;
572
573 if ( nodes_full_path == NULL )
574 return;
575
576 for ( i = 0; i < num_nodes && nodes_full_path[i] != NULL; i++ )
577 {
578 xfree(nodes_full_path[i]);
579 }
580
581 xfree(nodes_full_path);
582 }
583
add_nodes(struct overlay_track * tr,char ** nodes_full_path)584 static long add_nodes(struct overlay_track *tr, char **nodes_full_path)
585
586 {
587 int rc;
588 unsigned int j;
589 struct dt_device_node *overlay_node;
590
591 for ( j = 0; j < tr->num_nodes; j++ )
592 {
593 struct dt_device_node *prev_node, *next_node;
594
595 dt_dprintk("Adding node: %s\n", nodes_full_path[j]);
596
597 /* Find the newly added node in tr->dt_host_new by it's full path. */
598 overlay_node = dt_find_node_by_path_from(tr->dt_host_new,
599 nodes_full_path[j]);
600 if ( overlay_node == NULL )
601 return -EFAULT;
602
603 /*
604 * Find previous and next node to overlay_node in dt_host_new. We will
605 * need these nodes to fix the dt_host_new mapping. When overlay_node is
606 * take out of dt_host_new tree and added to dt_host, link between
607 * previous node and next_node is broken. We will need to refresh
608 * dt_host_new with correct linking for any other overlay nodes
609 * extraction in future.
610 */
611 dt_for_each_device_node(tr->dt_host_new, prev_node)
612 if ( prev_node->allnext == overlay_node )
613 break;
614
615 next_node = dt_find_next_node(tr->dt_host_new, overlay_node);
616
617 write_lock(&dt_host_lock);
618
619 /* Add the node to dt_host. */
620 rc = dt_overlay_add_node(overlay_node, overlay_node->parent->full_name);
621 if ( rc )
622 {
623 write_unlock(&dt_host_lock);
624
625 /* Node not added in dt_host. */
626 return rc;
627 }
628
629 prev_node->allnext = next_node;
630
631 overlay_node = dt_find_node_by_path(overlay_node->full_name);
632 if ( overlay_node == NULL )
633 {
634 /* Sanity check. But code will never come here. */
635 ASSERT_UNREACHABLE();
636 return -EFAULT;
637 }
638
639 write_unlock(&dt_host_lock);
640
641 /* Keep overlay_node address in tracker. */
642 tr->nodes_address[j] = (unsigned long)overlay_node;
643 }
644
645 return 0;
646 }
647 /*
648 * Adds device tree nodes under target node.
649 * We use tr->dt_host_new to unflatten the updated device_tree_flattened.
650 */
handle_add_overlay_nodes(void * overlay_fdt,uint32_t overlay_fdt_size)651 static long handle_add_overlay_nodes(void *overlay_fdt,
652 uint32_t overlay_fdt_size)
653 {
654 int rc;
655 unsigned int j;
656 struct dt_device_node *overlay_node;
657 struct overlay_track *tr = NULL;
658 char **nodes_full_path = NULL;
659 unsigned int new_fdt_size;
660
661 tr = xzalloc(struct overlay_track);
662 if ( tr == NULL )
663 return -ENOMEM;
664
665 new_fdt_size = fdt_totalsize(device_tree_flattened) +
666 fdt_totalsize(overlay_fdt);
667
668 tr->fdt = xzalloc_bytes(new_fdt_size);
669 if ( tr->fdt == NULL )
670 {
671 xfree(tr);
672 return -ENOMEM;
673 }
674
675 tr->num_nodes = overlay_node_count(overlay_fdt);
676 if ( tr->num_nodes == 0 )
677 {
678 xfree(tr->fdt);
679 xfree(tr);
680 return -ENOMEM;
681 }
682
683 tr->nodes_address = xzalloc_bytes(tr->num_nodes * sizeof(unsigned long));
684 if ( tr->nodes_address == NULL )
685 {
686 xfree(tr->fdt);
687 xfree(tr);
688 return -ENOMEM;
689 }
690
691 rc = check_overlay_fdt(overlay_fdt, overlay_fdt_size);
692 if ( rc )
693 {
694 xfree(tr->nodes_address);
695 xfree(tr->fdt);
696 xfree(tr);
697 return rc;
698 }
699
700 /*
701 * Keep a copy of overlay_fdt as fdt_overlay_apply will change the input
702 * overlay's content(magic) when applying overlay.
703 */
704 tr->overlay_fdt = xzalloc_bytes(overlay_fdt_size);
705 if ( tr->overlay_fdt == NULL )
706 {
707 xfree(tr->nodes_address);
708 xfree(tr->fdt);
709 xfree(tr);
710 return -ENOMEM;
711 }
712
713 memcpy(tr->overlay_fdt, overlay_fdt, overlay_fdt_size);
714
715 spin_lock(&overlay_lock);
716
717 memcpy(tr->fdt, device_tree_flattened,
718 fdt_totalsize(device_tree_flattened));
719
720 /* Open tr->fdt with more space to accommodate the overlay_fdt. */
721 rc = fdt_open_into(tr->fdt, tr->fdt, new_fdt_size);
722 if ( rc )
723 {
724 printk(XENLOG_ERR "Increasing fdt size to accommodate overlay_fdt failed with error %d\n",
725 rc);
726 goto err;
727 }
728
729 nodes_full_path = xzalloc_bytes(tr->num_nodes * sizeof(char *));
730 if ( nodes_full_path == NULL )
731 {
732 rc = -ENOMEM;
733 goto err;
734 }
735
736 /*
737 * overlay_get_nodes_info is called to get the node information from dtbo.
738 * This is done before fdt_overlay_apply() because the overlay apply will
739 * erase the magic of overlay_fdt.
740 */
741 rc = overlay_get_nodes_info(overlay_fdt, nodes_full_path);
742 if ( rc )
743 {
744 printk(XENLOG_ERR "Getting nodes information failed with error %d\n",
745 rc);
746 goto err;
747 }
748
749 rc = fdt_overlay_apply(tr->fdt, overlay_fdt);
750 if ( rc )
751 {
752 printk(XENLOG_ERR "Adding overlay node failed with error %d\n", rc);
753 goto err;
754 }
755
756 /*
757 * Check if any of the node already exists in dt_host. If node already exits
758 * we can return here as this overlay_fdt is not suitable for overlay ops.
759 */
760 for ( j = 0; j < tr->num_nodes; j++ )
761 {
762 overlay_node = dt_find_node_by_path(nodes_full_path[j]);
763 if ( overlay_node != NULL )
764 {
765 printk(XENLOG_ERR "node %s exists in device tree\n",
766 nodes_full_path[j]);
767 rc = -EINVAL;
768 goto err;
769 }
770 }
771
772 /*
773 * Unflatten the tr->fdt into a new dt_host.
774 * TODO: Check and add alias_scan() if it's needed for overlay in future.
775 */
776 rc = unflatten_device_tree(tr->fdt, &tr->dt_host_new);
777 if ( rc )
778 {
779 printk(XENLOG_ERR "unflatten_device_tree failed with error %d\n", rc);
780 goto err;
781 }
782
783 rc = add_nodes(tr, nodes_full_path);
784 if ( rc )
785 {
786 printk(XENLOG_ERR "Adding nodes failed. Removing the partially added nodes.\n");
787 goto remove_node;
788 }
789
790 INIT_LIST_HEAD(&tr->entry);
791 list_add_tail(&tr->entry, &overlay_tracker);
792
793 spin_unlock(&overlay_lock);
794
795 free_nodes_full_path(tr->num_nodes, nodes_full_path);
796
797 return rc;
798
799 /*
800 * Failure case. We need to remove the nodes, free tracker(if tr exists) and
801 * tr->dt_host_new.
802 */
803 remove_node:
804 tr->num_nodes = j;
805 rc = remove_nodes(tr);
806
807 if ( rc )
808 {
809 /*
810 * User needs to provide right overlay. Incorrect node information
811 * example parent node doesn't exist in dt_host etc can cause memory
812 * leaks as removing_nodes() will fail and this means nodes memory is
813 * not freed from tracker. Which may cause memory leaks. Ideally, these
814 * device tree related mistakes will be caught by fdt_overlay_apply()
815 * but given that we don't manage that code keeping this warning message
816 * is better here.
817 */
818 printk(XENLOG_ERR "Removing node failed.\n");
819 spin_unlock(&overlay_lock);
820
821 free_nodes_full_path(tr->num_nodes, nodes_full_path);
822
823 return rc;
824 }
825
826 err:
827 spin_unlock(&overlay_lock);
828
829 if ( tr->dt_host_new )
830 xfree(tr->dt_host_new);
831
832 free_nodes_full_path(tr->num_nodes, nodes_full_path);
833
834 xfree(tr->overlay_fdt);
835 xfree(tr->nodes_address);
836 xfree(tr->fdt);
837
838 xfree(tr);
839
840 return rc;
841 }
842
handle_attach_overlay_nodes(struct domain * d,const void * overlay_fdt,uint32_t overlay_fdt_size)843 static long handle_attach_overlay_nodes(struct domain *d,
844 const void *overlay_fdt,
845 uint32_t overlay_fdt_size)
846 {
847 int rc;
848 unsigned int j;
849 struct overlay_track *entry;
850
851 rc = check_overlay_fdt(overlay_fdt, overlay_fdt_size);
852 if ( rc )
853 return rc;
854
855 spin_lock(&overlay_lock);
856
857 entry = find_track_entry_from_tracker(overlay_fdt, overlay_fdt_size);
858 if ( entry == NULL )
859 {
860 rc = -EINVAL;
861 goto out;
862 }
863
864 entry->irq_ranges = rangeset_new(d, "Overlays: Interrupts", 0);
865 if (entry->irq_ranges == NULL)
866 {
867 rc = -ENOMEM;
868 printk(XENLOG_ERR "Creating IRQ rangeset failed");
869 goto out;
870 }
871
872 entry->iomem_ranges = rangeset_new(d, "Overlay: I/O Memory",
873 RANGESETF_prettyprint_hex);
874 if (entry->iomem_ranges == NULL)
875 {
876 rc = -ENOMEM;
877 printk(XENLOG_ERR "Creating IOMMU rangeset failed");
878 goto out;
879 }
880
881 for ( j = 0; j < entry->num_nodes; j++ )
882 {
883 struct dt_device_node *overlay_node;
884
885 overlay_node = (struct dt_device_node *)entry->nodes_address[j];
886 if ( overlay_node == NULL )
887 {
888 rc = -EINVAL;
889 goto out;
890 }
891
892 write_lock(&dt_host_lock);
893 rc = handle_device(d, overlay_node, p2m_mmio_direct_c,
894 entry->iomem_ranges, entry->irq_ranges);
895 write_unlock(&dt_host_lock);
896 if ( rc )
897 {
898 printk(XENLOG_ERR "Adding IRQ and IOMMU failed\n");
899 goto out;
900 }
901 }
902
903 spin_unlock(&overlay_lock);
904
905 return 0;
906
907 out:
908 spin_unlock(&overlay_lock);
909
910 if ( entry )
911 {
912 rangeset_destroy(entry->irq_ranges);
913 rangeset_destroy(entry->iomem_ranges);
914 }
915
916 return rc;
917 }
918
dt_overlay_sysctl(struct xen_sysctl_dt_overlay * op)919 long dt_overlay_sysctl(struct xen_sysctl_dt_overlay *op)
920 {
921 long ret;
922 void *overlay_fdt;
923
924 if ( op->overlay_op != XEN_SYSCTL_DT_OVERLAY_ADD &&
925 op->overlay_op != XEN_SYSCTL_DT_OVERLAY_REMOVE )
926 return -EOPNOTSUPP;
927
928 if ( op->overlay_fdt_size == 0 ||
929 op->overlay_fdt_size > DT_OVERLAY_MAX_SIZE )
930 return -EINVAL;
931
932 if ( op->pad[0] || op->pad[1] || op->pad[2] )
933 return -EINVAL;
934
935 overlay_fdt = xmalloc_bytes(op->overlay_fdt_size);
936
937 if ( overlay_fdt == NULL )
938 return -ENOMEM;
939
940 ret = copy_from_guest(overlay_fdt, op->overlay_fdt, op->overlay_fdt_size);
941 if ( ret )
942 {
943 gprintk(XENLOG_ERR, "copy from guest failed\n");
944 xfree(overlay_fdt);
945
946 return -EFAULT;
947 }
948
949 if ( op->overlay_op == XEN_SYSCTL_DT_OVERLAY_REMOVE )
950 ret = handle_remove_overlay_nodes(overlay_fdt, op->overlay_fdt_size);
951 else
952 ret = handle_add_overlay_nodes(overlay_fdt, op->overlay_fdt_size);
953
954 xfree(overlay_fdt);
955
956 return ret;
957 }
958
dt_overlay_domctl(struct domain * d,struct xen_domctl_dt_overlay * op)959 long dt_overlay_domctl(struct domain *d, struct xen_domctl_dt_overlay *op)
960 {
961 long ret;
962 void *overlay_fdt;
963
964 if ( op->overlay_op != XEN_DOMCTL_DT_OVERLAY_ATTACH )
965 return -EOPNOTSUPP;
966
967 if ( op->overlay_fdt_size == 0 ||
968 op->overlay_fdt_size > DT_OVERLAY_MAX_SIZE )
969 return -EINVAL;
970
971 if ( op->pad[0] || op->pad[1] || op->pad[2] )
972 return -EINVAL;
973
974 /* TODO: add support for non-1:1 domains using xen,reg */
975 if ( !is_domain_direct_mapped(d) )
976 return -EOPNOTSUPP;
977
978 overlay_fdt = xmalloc_bytes(op->overlay_fdt_size);
979
980 if ( overlay_fdt == NULL )
981 return -ENOMEM;
982
983 ret = copy_from_guest(overlay_fdt, op->overlay_fdt, op->overlay_fdt_size);
984 if ( ret )
985 {
986 gprintk(XENLOG_ERR, "copy from guest failed\n");
987 xfree(overlay_fdt);
988
989 return -EFAULT;
990 }
991
992 if ( op->overlay_op == XEN_DOMCTL_DT_OVERLAY_ATTACH )
993 ret = handle_attach_overlay_nodes(d, overlay_fdt, op->overlay_fdt_size);
994 else
995 ret = -EOPNOTSUPP;
996
997 xfree(overlay_fdt);
998
999 return ret;
1000 }
1001
1002 /*
1003 * Local variables:
1004 * mode: C
1005 * c-file-style: "BSD"
1006 * c-basic-offset: 4
1007 * indent-tabs-mode: nil
1008 * End:
1009 */
1010