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 /drivers/of/dynamic.c | |
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>
Diffstat (limited to 'drivers/of/dynamic.c')
-rw-r--r-- | drivers/of/dynamic.c | 344 |
1 files changed, 344 insertions, 0 deletions
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 | } | ||