diff options
| -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 */ |
