diff options
author | Pantelis Antoniou <pantelis.antoniou@konsulko.com> | 2014-07-04 12:58:49 -0400 |
---|---|---|
committer | Grant Likely <grant.likely@linaro.org> | 2014-07-23 19:29:15 -0400 |
commit | 201c910bd6898d81d4ac6685d0f421b7e10f3c5d (patch) | |
tree | aec8c406908e71c7ad89750c6e9f4f8d0c094747 | |
parent | 259092a35c7e11f1d4616b0f5b3ba7b851fe4fa6 (diff) |
of: Transactional DT support.
Introducing DT transactional support.
A DT transaction is a method which allows one to apply changes
in the live tree, in such a way that either the full set of changes
take effect, or the state of the tree can be rolled-back to the
state it was before it was attempted. An applied transaction
can be rolled-back at any time.
Documentation is in
Documentation/devicetree/changesets.txt
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
[glikely: Removed device notifiers and reworked to be more consistent]
Signed-off-by: Grant Likely <grant.likely@linaro.org>
-rw-r--r-- | Documentation/devicetree/changesets.txt | 40 | ||||
-rw-r--r-- | drivers/of/dynamic.c | 344 | ||||
-rw-r--r-- | drivers/of/of_private.h | 9 | ||||
-rw-r--r-- | drivers/of/selftest.c | 51 | ||||
-rw-r--r-- | drivers/of/testcase-data/testcases.dtsi | 10 | ||||
-rw-r--r-- | include/linux/of.h | 76 |
6 files changed, 530 insertions, 0 deletions
diff --git a/Documentation/devicetree/changesets.txt b/Documentation/devicetree/changesets.txt new file mode 100644 index 000000000000..935ba5acc34e --- /dev/null +++ b/Documentation/devicetree/changesets.txt | |||
@@ -0,0 +1,40 @@ | |||
1 | A DT changeset is a method which allows one to apply changes | ||
2 | in the live tree in such a way that either the full set of changes | ||
3 | will be applied, or none of them will be. If an error occurs partway | ||
4 | through applying the changeset, then the tree will be rolled back to the | ||
5 | previous state. A changeset can also be removed after it has been | ||
6 | applied. | ||
7 | |||
8 | When a changeset is applied, all of the changes get applied to the tree | ||
9 | at once before emitting OF_RECONFIG notifiers. This is so that the | ||
10 | receiver sees a complete and consistent state of the tree when it | ||
11 | receives the notifier. | ||
12 | |||
13 | The sequence of a changeset is as follows. | ||
14 | |||
15 | 1. of_changeset_init() - initializes a changeset | ||
16 | |||
17 | 2. A number of DT tree change calls, of_changeset_attach_node(), | ||
18 | of_changeset_detach_node(), of_changeset_add_property(), | ||
19 | of_changeset_remove_property, of_changeset_update_property() to prepare | ||
20 | a set of changes. No changes to the active tree are made at this point. | ||
21 | All the change operations are recorded in the of_changeset 'entries' | ||
22 | list. | ||
23 | |||
24 | 3. mutex_lock(of_mutex) - starts a changeset; The global of_mutex | ||
25 | ensures there can only be one editor at a time. | ||
26 | |||
27 | 4. of_changeset_apply() - Apply the changes to the tree. Either the | ||
28 | entire changeset will get applied, or if there is an error the tree will | ||
29 | be restored to the previous state | ||
30 | |||
31 | 5. mutex_unlock(of_mutex) - All operations complete, release the mutex | ||
32 | |||
33 | If a successfully applied changeset needs to be removed, it can be done | ||
34 | with the following sequence. | ||
35 | |||
36 | 1. mutex_lock(of_mutex) | ||
37 | |||
38 | 2. of_changeset_revert() | ||
39 | |||
40 | 3. mutex_unlock(of_mutex) | ||
diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c index 7bd5501736a6..c1002b7be786 100644 --- a/drivers/of/dynamic.c +++ b/drivers/of/dynamic.c | |||
@@ -314,3 +314,347 @@ struct device_node *__of_node_alloc(const char *full_name, gfp_t allocflags) | |||
314 | kfree(node); | 314 | kfree(node); |
315 | return NULL; | 315 | return NULL; |
316 | } | 316 | } |
317 | |||
318 | static void __of_changeset_entry_destroy(struct of_changeset_entry *ce) | ||
319 | { | ||
320 | of_node_put(ce->np); | ||
321 | list_del(&ce->node); | ||
322 | kfree(ce); | ||
323 | } | ||
324 | |||
325 | #ifdef DEBUG | ||
326 | static void __of_changeset_entry_dump(struct of_changeset_entry *ce) | ||
327 | { | ||
328 | switch (ce->action) { | ||
329 | case OF_RECONFIG_ADD_PROPERTY: | ||
330 | pr_debug("%p: %s %s/%s\n", | ||
331 | ce, "ADD_PROPERTY ", ce->np->full_name, | ||
332 | ce->prop->name); | ||
333 | break; | ||
334 | case OF_RECONFIG_REMOVE_PROPERTY: | ||
335 | pr_debug("%p: %s %s/%s\n", | ||
336 | ce, "REMOVE_PROPERTY", ce->np->full_name, | ||
337 | ce->prop->name); | ||
338 | break; | ||
339 | case OF_RECONFIG_UPDATE_PROPERTY: | ||
340 | pr_debug("%p: %s %s/%s\n", | ||
341 | ce, "UPDATE_PROPERTY", ce->np->full_name, | ||
342 | ce->prop->name); | ||
343 | break; | ||
344 | case OF_RECONFIG_ATTACH_NODE: | ||
345 | pr_debug("%p: %s %s\n", | ||
346 | ce, "ATTACH_NODE ", ce->np->full_name); | ||
347 | break; | ||
348 | case OF_RECONFIG_DETACH_NODE: | ||
349 | pr_debug("%p: %s %s\n", | ||
350 | ce, "DETACH_NODE ", ce->np->full_name); | ||
351 | break; | ||
352 | } | ||
353 | } | ||
354 | #else | ||
355 | static inline void __of_changeset_entry_dump(struct of_changeset_entry *ce) | ||
356 | { | ||
357 | /* empty */ | ||
358 | } | ||
359 | #endif | ||
360 | |||
361 | static void __of_changeset_entry_invert(struct of_changeset_entry *ce, | ||
362 | struct of_changeset_entry *rce) | ||
363 | { | ||
364 | memcpy(rce, ce, sizeof(*rce)); | ||
365 | |||
366 | switch (ce->action) { | ||
367 | case OF_RECONFIG_ATTACH_NODE: | ||
368 | rce->action = OF_RECONFIG_DETACH_NODE; | ||
369 | break; | ||
370 | case OF_RECONFIG_DETACH_NODE: | ||
371 | rce->action = OF_RECONFIG_ATTACH_NODE; | ||
372 | break; | ||
373 | case OF_RECONFIG_ADD_PROPERTY: | ||
374 | rce->action = OF_RECONFIG_REMOVE_PROPERTY; | ||
375 | break; | ||
376 | case OF_RECONFIG_REMOVE_PROPERTY: | ||
377 | rce->action = OF_RECONFIG_ADD_PROPERTY; | ||
378 | break; | ||
379 | case OF_RECONFIG_UPDATE_PROPERTY: | ||
380 | rce->old_prop = ce->prop; | ||
381 | rce->prop = ce->old_prop; | ||
382 | break; | ||
383 | } | ||
384 | } | ||
385 | |||
386 | static void __of_changeset_entry_notify(struct of_changeset_entry *ce, bool revert) | ||
387 | { | ||
388 | struct of_changeset_entry ce_inverted; | ||
389 | int ret; | ||
390 | |||
391 | if (revert) { | ||
392 | __of_changeset_entry_invert(ce, &ce_inverted); | ||
393 | ce = &ce_inverted; | ||
394 | } | ||
395 | |||
396 | switch (ce->action) { | ||
397 | case OF_RECONFIG_ATTACH_NODE: | ||
398 | case OF_RECONFIG_DETACH_NODE: | ||
399 | ret = of_reconfig_notify(ce->action, ce->np); | ||
400 | break; | ||
401 | case OF_RECONFIG_ADD_PROPERTY: | ||
402 | case OF_RECONFIG_REMOVE_PROPERTY: | ||
403 | case OF_RECONFIG_UPDATE_PROPERTY: | ||
404 | ret = of_property_notify(ce->action, ce->np, ce->prop, ce->old_prop); | ||
405 | break; | ||
406 | default: | ||
407 | pr_err("%s: invalid devicetree changeset action: %i\n", __func__, | ||
408 | (int)ce->action); | ||
409 | return; | ||
410 | } | ||
411 | |||
412 | if (ret) | ||
413 | pr_err("%s: notifier error @%s\n", __func__, ce->np->full_name); | ||
414 | } | ||
415 | |||
416 | static int __of_changeset_entry_apply(struct of_changeset_entry *ce) | ||
417 | { | ||
418 | struct property *old_prop, **propp; | ||
419 | unsigned long flags; | ||
420 | int ret = 0; | ||
421 | |||
422 | __of_changeset_entry_dump(ce); | ||
423 | |||
424 | raw_spin_lock_irqsave(&devtree_lock, flags); | ||
425 | switch (ce->action) { | ||
426 | case OF_RECONFIG_ATTACH_NODE: | ||
427 | __of_attach_node(ce->np); | ||
428 | break; | ||
429 | case OF_RECONFIG_DETACH_NODE: | ||
430 | __of_detach_node(ce->np); | ||
431 | break; | ||
432 | case OF_RECONFIG_ADD_PROPERTY: | ||
433 | /* If the property is in deadprops then it must be removed */ | ||
434 | for (propp = &ce->np->deadprops; *propp; propp = &(*propp)->next) { | ||
435 | if (*propp == ce->prop) { | ||
436 | *propp = ce->prop->next; | ||
437 | ce->prop->next = NULL; | ||
438 | break; | ||
439 | } | ||
440 | } | ||
441 | |||
442 | ret = __of_add_property(ce->np, ce->prop); | ||
443 | if (ret) { | ||
444 | pr_err("%s: add_property failed @%s/%s\n", | ||
445 | __func__, ce->np->full_name, | ||
446 | ce->prop->name); | ||
447 | break; | ||
448 | } | ||
449 | break; | ||
450 | case OF_RECONFIG_REMOVE_PROPERTY: | ||
451 | ret = __of_remove_property(ce->np, ce->prop); | ||
452 | if (ret) { | ||
453 | pr_err("%s: remove_property failed @%s/%s\n", | ||
454 | __func__, ce->np->full_name, | ||
455 | ce->prop->name); | ||
456 | break; | ||
457 | } | ||
458 | break; | ||
459 | |||
460 | case OF_RECONFIG_UPDATE_PROPERTY: | ||
461 | /* If the property is in deadprops then it must be removed */ | ||
462 | for (propp = &ce->np->deadprops; *propp; propp = &(*propp)->next) { | ||
463 | if (*propp == ce->prop) { | ||
464 | *propp = ce->prop->next; | ||
465 | ce->prop->next = NULL; | ||
466 | break; | ||
467 | } | ||
468 | } | ||
469 | |||
470 | ret = __of_update_property(ce->np, ce->prop, &old_prop); | ||
471 | if (ret) { | ||
472 | pr_err("%s: update_property failed @%s/%s\n", | ||
473 | __func__, ce->np->full_name, | ||
474 | ce->prop->name); | ||
475 | break; | ||
476 | } | ||
477 | break; | ||
478 | default: | ||
479 | ret = -EINVAL; | ||
480 | } | ||
481 | raw_spin_unlock_irqrestore(&devtree_lock, flags); | ||
482 | |||
483 | if (ret) | ||
484 | return ret; | ||
485 | |||
486 | switch (ce->action) { | ||
487 | case OF_RECONFIG_ATTACH_NODE: | ||
488 | __of_attach_node_sysfs(ce->np); | ||
489 | break; | ||
490 | case OF_RECONFIG_DETACH_NODE: | ||
491 | __of_detach_node_sysfs(ce->np); | ||
492 | break; | ||
493 | case OF_RECONFIG_ADD_PROPERTY: | ||
494 | /* ignore duplicate names */ | ||
495 | __of_add_property_sysfs(ce->np, ce->prop); | ||
496 | break; | ||
497 | case OF_RECONFIG_REMOVE_PROPERTY: | ||
498 | __of_remove_property_sysfs(ce->np, ce->prop); | ||
499 | break; | ||
500 | case OF_RECONFIG_UPDATE_PROPERTY: | ||
501 | __of_update_property_sysfs(ce->np, ce->prop, ce->old_prop); | ||
502 | break; | ||
503 | } | ||
504 | |||
505 | return 0; | ||
506 | } | ||
507 | |||
508 | static inline int __of_changeset_entry_revert(struct of_changeset_entry *ce) | ||
509 | { | ||
510 | struct of_changeset_entry ce_inverted; | ||
511 | |||
512 | __of_changeset_entry_invert(ce, &ce_inverted); | ||
513 | return __of_changeset_entry_apply(&ce_inverted); | ||
514 | } | ||
515 | |||
516 | /** | ||
517 | * of_changeset_init - Initialize a changeset for use | ||
518 | * | ||
519 | * @ocs: changeset pointer | ||
520 | * | ||
521 | * Initialize a changeset structure | ||
522 | */ | ||
523 | void of_changeset_init(struct of_changeset *ocs) | ||
524 | { | ||
525 | memset(ocs, 0, sizeof(*ocs)); | ||
526 | INIT_LIST_HEAD(&ocs->entries); | ||
527 | } | ||
528 | |||
529 | /** | ||
530 | * of_changeset_destroy - Destroy a changeset | ||
531 | * | ||
532 | * @ocs: changeset pointer | ||
533 | * | ||
534 | * Destroys a changeset. Note that if a changeset is applied, | ||
535 | * its changes to the tree cannot be reverted. | ||
536 | */ | ||
537 | void of_changeset_destroy(struct of_changeset *ocs) | ||
538 | { | ||
539 | struct of_changeset_entry *ce, *cen; | ||
540 | |||
541 | list_for_each_entry_safe_reverse(ce, cen, &ocs->entries, node) | ||
542 | __of_changeset_entry_destroy(ce); | ||
543 | } | ||
544 | |||
545 | /** | ||
546 | * of_changeset_apply - Applies a changeset | ||
547 | * | ||
548 | * @ocs: changeset pointer | ||
549 | * | ||
550 | * Applies a changeset to the live tree. | ||
551 | * Any side-effects of live tree state changes are applied here on | ||
552 | * sucess, like creation/destruction of devices and side-effects | ||
553 | * like creation of sysfs properties and directories. | ||
554 | * Returns 0 on success, a negative error value in case of an error. | ||
555 | * On error the partially applied effects are reverted. | ||
556 | */ | ||
557 | int of_changeset_apply(struct of_changeset *ocs) | ||
558 | { | ||
559 | struct of_changeset_entry *ce; | ||
560 | int ret; | ||
561 | |||
562 | /* perform the rest of the work */ | ||
563 | pr_debug("of_changeset: applying...\n"); | ||
564 | list_for_each_entry(ce, &ocs->entries, node) { | ||
565 | ret = __of_changeset_entry_apply(ce); | ||
566 | if (ret) { | ||
567 | pr_err("%s: Error applying changeset (%d)\n", __func__, ret); | ||
568 | list_for_each_entry_continue_reverse(ce, &ocs->entries, node) | ||
569 | __of_changeset_entry_revert(ce); | ||
570 | return ret; | ||
571 | } | ||
572 | } | ||
573 | pr_debug("of_changeset: applied, emitting notifiers.\n"); | ||
574 | |||
575 | /* drop the global lock while emitting notifiers */ | ||
576 | mutex_unlock(&of_mutex); | ||
577 | list_for_each_entry(ce, &ocs->entries, node) | ||
578 | __of_changeset_entry_notify(ce, 0); | ||
579 | mutex_lock(&of_mutex); | ||
580 | pr_debug("of_changeset: notifiers sent.\n"); | ||
581 | |||
582 | return 0; | ||
583 | } | ||
584 | |||
585 | /** | ||
586 | * of_changeset_revert - Reverts an applied changeset | ||
587 | * | ||
588 | * @ocs: changeset pointer | ||
589 | * | ||
590 | * Reverts a changeset returning the state of the tree to what it | ||
591 | * was before the application. | ||
592 | * Any side-effects like creation/destruction of devices and | ||
593 | * removal of sysfs properties and directories are applied. | ||
594 | * Returns 0 on success, a negative error value in case of an error. | ||
595 | */ | ||
596 | int of_changeset_revert(struct of_changeset *ocs) | ||
597 | { | ||
598 | struct of_changeset_entry *ce; | ||
599 | int ret; | ||
600 | |||
601 | pr_debug("of_changeset: reverting...\n"); | ||
602 | list_for_each_entry_reverse(ce, &ocs->entries, node) { | ||
603 | ret = __of_changeset_entry_revert(ce); | ||
604 | if (ret) { | ||
605 | pr_err("%s: Error reverting changeset (%d)\n", __func__, ret); | ||
606 | list_for_each_entry_continue(ce, &ocs->entries, node) | ||
607 | __of_changeset_entry_apply(ce); | ||
608 | return ret; | ||
609 | } | ||
610 | } | ||
611 | pr_debug("of_changeset: reverted, emitting notifiers.\n"); | ||
612 | |||
613 | /* drop the global lock while emitting notifiers */ | ||
614 | mutex_unlock(&of_mutex); | ||
615 | list_for_each_entry_reverse(ce, &ocs->entries, node) | ||
616 | __of_changeset_entry_notify(ce, 1); | ||
617 | mutex_lock(&of_mutex); | ||
618 | pr_debug("of_changeset: notifiers sent.\n"); | ||
619 | |||
620 | return 0; | ||
621 | } | ||
622 | |||
623 | /** | ||
624 | * of_changeset_action - Perform a changeset action | ||
625 | * | ||
626 | * @ocs: changeset pointer | ||
627 | * @action: action to perform | ||
628 | * @np: Pointer to device node | ||
629 | * @prop: Pointer to property | ||
630 | * | ||
631 | * On action being one of: | ||
632 | * + OF_RECONFIG_ATTACH_NODE | ||
633 | * + OF_RECONFIG_DETACH_NODE, | ||
634 | * + OF_RECONFIG_ADD_PROPERTY | ||
635 | * + OF_RECONFIG_REMOVE_PROPERTY, | ||
636 | * + OF_RECONFIG_UPDATE_PROPERTY | ||
637 | * Returns 0 on success, a negative error value in case of an error. | ||
638 | */ | ||
639 | int of_changeset_action(struct of_changeset *ocs, unsigned long action, | ||
640 | struct device_node *np, struct property *prop) | ||
641 | { | ||
642 | struct of_changeset_entry *ce; | ||
643 | |||
644 | ce = kzalloc(sizeof(*ce), GFP_KERNEL); | ||
645 | if (!ce) { | ||
646 | pr_err("%s: Failed to allocate\n", __func__); | ||
647 | return -ENOMEM; | ||
648 | } | ||
649 | /* get a reference to the node */ | ||
650 | ce->action = action; | ||
651 | ce->np = of_node_get(np); | ||
652 | ce->prop = prop; | ||
653 | |||
654 | if (action == OF_RECONFIG_UPDATE_PROPERTY && prop) | ||
655 | ce->old_prop = of_find_property(np, prop->name, NULL); | ||
656 | |||
657 | /* add it to the list */ | ||
658 | list_add_tail(&ce->node, &ocs->entries); | ||
659 | return 0; | ||
660 | } | ||
diff --git a/drivers/of/of_private.h b/drivers/of/of_private.h index f69ccb1fa308..858e0a5d9a11 100644 --- a/drivers/of/of_private.h +++ b/drivers/of/of_private.h | |||
@@ -81,4 +81,13 @@ extern int __of_attach_node_sysfs(struct device_node *np); | |||
81 | extern void __of_detach_node(struct device_node *np); | 81 | extern void __of_detach_node(struct device_node *np); |
82 | extern void __of_detach_node_sysfs(struct device_node *np); | 82 | extern void __of_detach_node_sysfs(struct device_node *np); |
83 | 83 | ||
84 | /* iterators for transactions, used for overlays */ | ||
85 | /* forward iterator */ | ||
86 | #define for_each_transaction_entry(_oft, _te) \ | ||
87 | list_for_each_entry(_te, &(_oft)->te_list, node) | ||
88 | |||
89 | /* reverse iterator */ | ||
90 | #define for_each_transaction_entry_reverse(_oft, _te) \ | ||
91 | list_for_each_entry_reverse(_te, &(_oft)->te_list, node) | ||
92 | |||
84 | #endif /* _LINUX_OF_PRIVATE_H */ | 93 | #endif /* _LINUX_OF_PRIVATE_H */ |
diff --git a/drivers/of/selftest.c b/drivers/of/selftest.c index ee2166f0f36a..04e39a183e53 100644 --- a/drivers/of/selftest.c +++ b/drivers/of/selftest.c | |||
@@ -293,6 +293,56 @@ static void __init of_selftest_property_copy(void) | |||
293 | #endif | 293 | #endif |
294 | } | 294 | } |
295 | 295 | ||
296 | static void __init of_selftest_changeset(void) | ||
297 | { | ||
298 | #ifdef CONFIG_OF_DYNAMIC | ||
299 | struct property *ppadd, padd = { .name = "prop-add", .length = 0, .value = "" }; | ||
300 | struct property *ppupdate, pupdate = { .name = "prop-update", .length = 5, .value = "abcd" }; | ||
301 | struct property *ppremove; | ||
302 | struct device_node *n1, *n2, *n21, *nremove, *parent; | ||
303 | struct of_changeset chgset; | ||
304 | |||
305 | of_changeset_init(&chgset); | ||
306 | n1 = __of_node_alloc("/testcase-data/changeset/n1", GFP_KERNEL); | ||
307 | selftest(n1, "testcase setup failure\n"); | ||
308 | n2 = __of_node_alloc("/testcase-data/changeset/n2", GFP_KERNEL); | ||
309 | selftest(n2, "testcase setup failure\n"); | ||
310 | n21 = __of_node_alloc("/testcase-data/changeset/n2/n21", GFP_KERNEL); | ||
311 | selftest(n21, "testcase setup failure %p\n", n21); | ||
312 | nremove = of_find_node_by_path("/testcase-data/changeset/node-remove"); | ||
313 | selftest(nremove, "testcase setup failure\n"); | ||
314 | ppadd = __of_prop_dup(&padd, GFP_KERNEL); | ||
315 | selftest(ppadd, "testcase setup failure\n"); | ||
316 | ppupdate = __of_prop_dup(&pupdate, GFP_KERNEL); | ||
317 | selftest(ppupdate, "testcase setup failure\n"); | ||
318 | parent = nremove->parent; | ||
319 | n1->parent = parent; | ||
320 | n2->parent = parent; | ||
321 | n21->parent = n2; | ||
322 | n2->child = n21; | ||
323 | ppremove = of_find_property(parent, "prop-remove", NULL); | ||
324 | selftest(ppremove, "failed to find removal prop"); | ||
325 | |||
326 | of_changeset_init(&chgset); | ||
327 | selftest(!of_changeset_attach_node(&chgset, n1), "fail attach n1\n"); | ||
328 | selftest(!of_changeset_attach_node(&chgset, n2), "fail attach n2\n"); | ||
329 | selftest(!of_changeset_detach_node(&chgset, nremove), "fail remove node\n"); | ||
330 | selftest(!of_changeset_attach_node(&chgset, n21), "fail attach n21\n"); | ||
331 | selftest(!of_changeset_add_property(&chgset, parent, ppadd), "fail add prop\n"); | ||
332 | selftest(!of_changeset_update_property(&chgset, parent, ppupdate), "fail update prop\n"); | ||
333 | selftest(!of_changeset_remove_property(&chgset, parent, ppremove), "fail remove prop\n"); | ||
334 | mutex_lock(&of_mutex); | ||
335 | selftest(!of_changeset_apply(&chgset), "apply failed\n"); | ||
336 | mutex_unlock(&of_mutex); | ||
337 | |||
338 | mutex_lock(&of_mutex); | ||
339 | selftest(!of_changeset_revert(&chgset), "revert failed\n"); | ||
340 | mutex_unlock(&of_mutex); | ||
341 | |||
342 | of_changeset_destroy(&chgset); | ||
343 | #endif | ||
344 | } | ||
345 | |||
296 | static void __init of_selftest_parse_interrupts(void) | 346 | static void __init of_selftest_parse_interrupts(void) |
297 | { | 347 | { |
298 | struct device_node *np; | 348 | struct device_node *np; |
@@ -561,6 +611,7 @@ static int __init of_selftest(void) | |||
561 | of_selftest_parse_phandle_with_args(); | 611 | of_selftest_parse_phandle_with_args(); |
562 | of_selftest_property_match_string(); | 612 | of_selftest_property_match_string(); |
563 | of_selftest_property_copy(); | 613 | of_selftest_property_copy(); |
614 | of_selftest_changeset(); | ||
564 | of_selftest_parse_interrupts(); | 615 | of_selftest_parse_interrupts(); |
565 | of_selftest_parse_interrupts_extended(); | 616 | of_selftest_parse_interrupts_extended(); |
566 | of_selftest_match_node(); | 617 | of_selftest_match_node(); |
diff --git a/drivers/of/testcase-data/testcases.dtsi b/drivers/of/testcase-data/testcases.dtsi index 6d8d980ac858..669bb07df142 100644 --- a/drivers/of/testcase-data/testcases.dtsi +++ b/drivers/of/testcase-data/testcases.dtsi | |||
@@ -1,3 +1,13 @@ | |||
1 | / { | ||
2 | testcase-data { | ||
3 | changeset { | ||
4 | prop-update = "hello"; | ||
5 | prop-remove = "world"; | ||
6 | node-remove { | ||
7 | }; | ||
8 | }; | ||
9 | }; | ||
10 | }; | ||
1 | #include "tests-phandle.dtsi" | 11 | #include "tests-phandle.dtsi" |
2 | #include "tests-interrupts.dtsi" | 12 | #include "tests-interrupts.dtsi" |
3 | #include "tests-match.dtsi" | 13 | #include "tests-match.dtsi" |
diff --git a/include/linux/of.h b/include/linux/of.h index 400f18cb4fff..bc91fbb13ce8 100644 --- a/include/linux/of.h +++ b/include/linux/of.h | |||
@@ -786,4 +786,80 @@ typedef void (*of_init_fn_1)(struct device_node *); | |||
786 | #define OF_DECLARE_2(table, name, compat, fn) \ | 786 | #define OF_DECLARE_2(table, name, compat, fn) \ |
787 | _OF_DECLARE(table, name, compat, fn, of_init_fn_2) | 787 | _OF_DECLARE(table, name, compat, fn, of_init_fn_2) |
788 | 788 | ||
789 | /** | ||
790 | * struct of_changeset_entry - Holds a changeset entry | ||
791 | * | ||
792 | * @node: list_head for the log list | ||
793 | * @action: notifier action | ||
794 | * @np: pointer to the device node affected | ||
795 | * @prop: pointer to the property affected | ||
796 | * @old_prop: hold a pointer to the original property | ||
797 | * | ||
798 | * Every modification of the device tree during a changeset | ||
799 | * is held in a list of of_changeset_entry structures. | ||
800 | * That way we can recover from a partial application, or we can | ||
801 | * revert the changeset | ||
802 | */ | ||
803 | struct of_changeset_entry { | ||
804 | struct list_head node; | ||
805 | unsigned long action; | ||
806 | struct device_node *np; | ||
807 | struct property *prop; | ||
808 | struct property *old_prop; | ||
809 | }; | ||
810 | |||
811 | /** | ||
812 | * struct of_changeset - changeset tracker structure | ||
813 | * | ||
814 | * @entries: list_head for the changeset entries | ||
815 | * | ||
816 | * changesets are a convenient way to apply bulk changes to the | ||
817 | * live tree. In case of an error, changes are rolled-back. | ||
818 | * changesets live on after initial application, and if not | ||
819 | * destroyed after use, they can be reverted in one single call. | ||
820 | */ | ||
821 | struct of_changeset { | ||
822 | struct list_head entries; | ||
823 | }; | ||
824 | |||
825 | #ifdef CONFIG_OF_DYNAMIC | ||
826 | extern void of_changeset_init(struct of_changeset *ocs); | ||
827 | extern void of_changeset_destroy(struct of_changeset *ocs); | ||
828 | extern int of_changeset_apply(struct of_changeset *ocs); | ||
829 | extern int of_changeset_revert(struct of_changeset *ocs); | ||
830 | extern int of_changeset_action(struct of_changeset *ocs, | ||
831 | unsigned long action, struct device_node *np, | ||
832 | struct property *prop); | ||
833 | |||
834 | static inline int of_changeset_attach_node(struct of_changeset *ocs, | ||
835 | struct device_node *np) | ||
836 | { | ||
837 | return of_changeset_action(ocs, OF_RECONFIG_ATTACH_NODE, np, NULL); | ||
838 | } | ||
839 | |||
840 | static inline int of_changeset_detach_node(struct of_changeset *ocs, | ||
841 | struct device_node *np) | ||
842 | { | ||
843 | return of_changeset_action(ocs, OF_RECONFIG_DETACH_NODE, np, NULL); | ||
844 | } | ||
845 | |||
846 | static inline int of_changeset_add_property(struct of_changeset *ocs, | ||
847 | struct device_node *np, struct property *prop) | ||
848 | { | ||
849 | return of_changeset_action(ocs, OF_RECONFIG_ADD_PROPERTY, np, prop); | ||
850 | } | ||
851 | |||
852 | static inline int of_changeset_remove_property(struct of_changeset *ocs, | ||
853 | struct device_node *np, struct property *prop) | ||
854 | { | ||
855 | return of_changeset_action(ocs, OF_RECONFIG_REMOVE_PROPERTY, np, prop); | ||
856 | } | ||
857 | |||
858 | static inline int of_changeset_update_property(struct of_changeset *ocs, | ||
859 | struct device_node *np, struct property *prop) | ||
860 | { | ||
861 | return of_changeset_action(ocs, OF_RECONFIG_UPDATE_PROPERTY, np, prop); | ||
862 | } | ||
863 | #endif | ||
864 | |||
789 | #endif /* _LINUX_OF_H */ | 865 | #endif /* _LINUX_OF_H */ |