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 <[email protected]>
[glikely: Removed device notifiers and reworked to be more consistent]
Signed-off-by: Grant Likely <[email protected]>
This commit is contained in:
Pantelis Antoniou 2014-07-04 19:58:49 +03:00 • committed by Grant Likely
commit 201c910bd6
6 changed files with 530 additions and 0 deletions

View file

@ -314,3 +314,347 @@ struct device_node *__of_node_alloc(const char *full_name, gfp_t allocflags)
kfree(node);
return NULL;
}
static void __of_changeset_entry_destroy(struct of_changeset_entry *ce)
{
of_node_put(ce->np);
list_del(&ce->node);
kfree(ce);
}
#ifdef DEBUG
static void __of_changeset_entry_dump(struct of_changeset_entry *ce)
{
switch (ce->action) {
case OF_RECONFIG_ADD_PROPERTY:
pr_debug("%p: %s %s/%s\n",
ce, "ADD_PROPERTY ", ce->np->full_name,
ce->prop->name);
break;
case OF_RECONFIG_REMOVE_PROPERTY:
pr_debug("%p: %s %s/%s\n",
ce, "REMOVE_PROPERTY", ce->np->full_name,
ce->prop->name);
break;
case OF_RECONFIG_UPDATE_PROPERTY:
pr_debug("%p: %s %s/%s\n",
ce, "UPDATE_PROPERTY", ce->np->full_name,
ce->prop->name);
break;
case OF_RECONFIG_ATTACH_NODE:
pr_debug("%p: %s %s\n",
ce, "ATTACH_NODE ", ce->np->full_name);
break;
case OF_RECONFIG_DETACH_NODE:
pr_debug("%p: %s %s\n",
ce, "DETACH_NODE ", ce->np->full_name);
break;
}
}
#else
static inline void __of_changeset_entry_dump(struct of_changeset_entry *ce)
{
/* empty */
}
#endif
static void __of_changeset_entry_invert(struct of_changeset_entry *ce,
struct of_changeset_entry *rce)
{
memcpy(rce, ce, sizeof(*rce));
switch (ce->action) {
case OF_RECONFIG_ATTACH_NODE:
rce->action = OF_RECONFIG_DETACH_NODE;
break;
case OF_RECONFIG_DETACH_NODE:
rce->action = OF_RECONFIG_ATTACH_NODE;
break;
case OF_RECONFIG_ADD_PROPERTY:
rce->action = OF_RECONFIG_REMOVE_PROPERTY;
break;
case OF_RECONFIG_REMOVE_PROPERTY:
rce->action = OF_RECONFIG_ADD_PROPERTY;
break;
case OF_RECONFIG_UPDATE_PROPERTY:
rce->old_prop = ce->prop;
rce->prop = ce->old_prop;
break;
}
}
static void __of_changeset_entry_notify(struct of_changeset_entry *ce, bool revert)
{
struct of_changeset_entry ce_inverted;
int ret;
if (revert) {
__of_changeset_entry_invert(ce, &ce_inverted);
ce = &ce_inverted;
}
switch (ce->action) {
case OF_RECONFIG_ATTACH_NODE:
case OF_RECONFIG_DETACH_NODE:
ret = of_reconfig_notify(ce->action, ce->np);
break;
case OF_RECONFIG_ADD_PROPERTY:
case OF_RECONFIG_REMOVE_PROPERTY:
case OF_RECONFIG_UPDATE_PROPERTY:
ret = of_property_notify(ce->action, ce->np, ce->prop, ce->old_prop);
break;
default:
pr_err("%s: invalid devicetree changeset action: %i\n", __func__,
(int)ce->action);
return;
}
if (ret)
pr_err("%s: notifier error @%s\n", __func__, ce->np->full_name);
}
static int __of_changeset_entry_apply(struct of_changeset_entry *ce)
{
struct property *old_prop, **propp;
unsigned long flags;
int ret = 0;
__of_changeset_entry_dump(ce);
raw_spin_lock_irqsave(&devtree_lock, flags);
switch (ce->action) {
case OF_RECONFIG_ATTACH_NODE:
__of_attach_node(ce->np);
break;
case OF_RECONFIG_DETACH_NODE:
__of_detach_node(ce->np);
break;
case OF_RECONFIG_ADD_PROPERTY:
/* If the property is in deadprops then it must be removed */
for (propp = &ce->np->deadprops; *propp; propp = &(*propp)->next) {
if (*propp == ce->prop) {
*propp = ce->prop->next;
ce->prop->next = NULL;
break;
}
}
ret = __of_add_property(ce->np, ce->prop);
if (ret) {
pr_err("%s: add_property failed @%s/%s\n",
__func__, ce->np->full_name,
ce->prop->name);
break;
}
break;
case OF_RECONFIG_REMOVE_PROPERTY:
ret = __of_remove_property(ce->np, ce->prop);
if (ret) {
pr_err("%s: remove_property failed @%s/%s\n",
__func__, ce->np->full_name,
ce->prop->name);
break;
}
break;
case OF_RECONFIG_UPDATE_PROPERTY:
/* If the property is in deadprops then it must be removed */
for (propp = &ce->np->deadprops; *propp; propp = &(*propp)->next) {
if (*propp == ce->prop) {
*propp = ce->prop->next;
ce->prop->next = NULL;
break;
}
}
ret = __of_update_property(ce->np, ce->prop, &old_prop);
if (ret) {
pr_err("%s: update_property failed @%s/%s\n",
__func__, ce->np->full_name,
ce->prop->name);
break;
}
break;
default:
ret = -EINVAL;
}
raw_spin_unlock_irqrestore(&devtree_lock, flags);
if (ret)
return ret;
switch (ce->action) {
case OF_RECONFIG_ATTACH_NODE:
__of_attach_node_sysfs(ce->np);
break;
case OF_RECONFIG_DETACH_NODE:
__of_detach_node_sysfs(ce->np);
break;
case OF_RECONFIG_ADD_PROPERTY:
/* ignore duplicate names */
__of_add_property_sysfs(ce->np, ce->prop);
break;
case OF_RECONFIG_REMOVE_PROPERTY:
__of_remove_property_sysfs(ce->np, ce->prop);
break;
case OF_RECONFIG_UPDATE_PROPERTY:
__of_update_property_sysfs(ce->np, ce->prop, ce->old_prop);
break;
}
return 0;
}
static inline int __of_changeset_entry_revert(struct of_changeset_entry *ce)
{
struct of_changeset_entry ce_inverted;
__of_changeset_entry_invert(ce, &ce_inverted);
return __of_changeset_entry_apply(&ce_inverted);
}
/**
* of_changeset_init - Initialize a changeset for use
*
* @ocs: changeset pointer
*
* Initialize a changeset structure
*/
void of_changeset_init(struct of_changeset *ocs)
{
memset(ocs, 0, sizeof(*ocs));
INIT_LIST_HEAD(&ocs->entries);
}
/**
* of_changeset_destroy - Destroy a changeset
*
* @ocs: changeset pointer
*
* Destroys a changeset. Note that if a changeset is applied,
* its changes to the tree cannot be reverted.
*/
void of_changeset_destroy(struct of_changeset *ocs)
{
struct of_changeset_entry *ce, *cen;
list_for_each_entry_safe_reverse(ce, cen, &ocs->entries, node)
__of_changeset_entry_destroy(ce);
}
/**
* of_changeset_apply - Applies a changeset
*
* @ocs: changeset pointer
*
* Applies a changeset to the live tree.
* Any side-effects of live tree state changes are applied here on
* sucess, like creation/destruction of devices and side-effects
* like creation of sysfs properties and directories.
* Returns 0 on success, a negative error value in case of an error.
* On error the partially applied effects are reverted.
*/
int of_changeset_apply(struct of_changeset *ocs)
{
struct of_changeset_entry *ce;
int ret;
/* perform the rest of the work */
pr_debug("of_changeset: applying...\n");
list_for_each_entry(ce, &ocs->entries, node) {
ret = __of_changeset_entry_apply(ce);
if (ret) {
pr_err("%s: Error applying changeset (%d)\n", __func__, ret);
list_for_each_entry_continue_reverse(ce, &ocs->entries, node)
__of_changeset_entry_revert(ce);
return ret;
}
}
pr_debug("of_changeset: applied, emitting notifiers.\n");
/* drop the global lock while emitting notifiers */
mutex_unlock(&of_mutex);
list_for_each_entry(ce, &ocs->entries, node)
__of_changeset_entry_notify(ce, 0);
mutex_lock(&of_mutex);
pr_debug("of_changeset: notifiers sent.\n");
return 0;
}
/**
* of_changeset_revert - Reverts an applied changeset
*
* @ocs: changeset pointer
*
* Reverts a changeset returning the state of the tree to what it
* was before the application.
* Any side-effects like creation/destruction of devices and
* removal of sysfs properties and directories are applied.
* Returns 0 on success, a negative error value in case of an error.
*/
int of_changeset_revert(struct of_changeset *ocs)
{
struct of_changeset_entry *ce;
int ret;
pr_debug("of_changeset: reverting...\n");
list_for_each_entry_reverse(ce, &ocs->entries, node) {
ret = __of_changeset_entry_revert(ce);
if (ret) {
pr_err("%s: Error reverting changeset (%d)\n", __func__, ret);
list_for_each_entry_continue(ce, &ocs->entries, node)
__of_changeset_entry_apply(ce);
return ret;
}
}
pr_debug("of_changeset: reverted, emitting notifiers.\n");
/* drop the global lock while emitting notifiers */
mutex_unlock(&of_mutex);
list_for_each_entry_reverse(ce, &ocs->entries, node)
__of_changeset_entry_notify(ce, 1);
mutex_lock(&of_mutex);
pr_debug("of_changeset: notifiers sent.\n");
return 0;
}
/**
* of_changeset_action - Perform a changeset action
*
* @ocs: changeset pointer
* @action: action to perform
* @np: Pointer to device node
* @prop: Pointer to property
*
* On action being one of:
* + OF_RECONFIG_ATTACH_NODE
* + OF_RECONFIG_DETACH_NODE,
* + OF_RECONFIG_ADD_PROPERTY
* + OF_RECONFIG_REMOVE_PROPERTY,
* + OF_RECONFIG_UPDATE_PROPERTY
* Returns 0 on success, a negative error value in case of an error.
*/
int of_changeset_action(struct of_changeset *ocs, unsigned long action,
struct device_node *np, struct property *prop)
{
struct of_changeset_entry *ce;
ce = kzalloc(sizeof(*ce), GFP_KERNEL);
if (!ce) {
pr_err("%s: Failed to allocate\n", __func__);
return -ENOMEM;
}
/* get a reference to the node */
ce->action = action;
ce->np = of_node_get(np);
ce->prop = prop;
if (action == OF_RECONFIG_UPDATE_PROPERTY && prop)
ce->old_prop = of_find_property(np, prop->name, NULL);
/* add it to the list */
list_add_tail(&ce->node, &ocs->entries);
return 0;
}

View file

@ -81,4 +81,13 @@ extern int __of_attach_node_sysfs(struct device_node *np);
extern void __of_detach_node(struct device_node *np);
extern void __of_detach_node_sysfs(struct device_node *np);
/* iterators for transactions, used for overlays */
/* forward iterator */
#define for_each_transaction_entry(_oft, _te) \
list_for_each_entry(_te, &(_oft)->te_list, node)
/* reverse iterator */
#define for_each_transaction_entry_reverse(_oft, _te) \
list_for_each_entry_reverse(_te, &(_oft)->te_list, node)
#endif /* _LINUX_OF_PRIVATE_H */

View file

@ -293,6 +293,56 @@ static void __init of_selftest_property_copy(void)
#endif
}
static void __init of_selftest_changeset(void)
{
#ifdef CONFIG_OF_DYNAMIC
struct property *ppadd, padd = { .name = "prop-add", .length = 0, .value = "" };
struct property *ppupdate, pupdate = { .name = "prop-update", .length = 5, .value = "abcd" };
struct property *ppremove;
struct device_node *n1, *n2, *n21, *nremove, *parent;
struct of_changeset chgset;
of_changeset_init(&chgset);
n1 = __of_node_alloc("/testcase-data/changeset/n1", GFP_KERNEL);
selftest(n1, "testcase setup failure\n");
n2 = __of_node_alloc("/testcase-data/changeset/n2", GFP_KERNEL);
selftest(n2, "testcase setup failure\n");
n21 = __of_node_alloc("/testcase-data/changeset/n2/n21", GFP_KERNEL);
selftest(n21, "testcase setup failure %p\n", n21);
nremove = of_find_node_by_path("/testcase-data/changeset/node-remove");
selftest(nremove, "testcase setup failure\n");
ppadd = __of_prop_dup(&padd, GFP_KERNEL);
selftest(ppadd, "testcase setup failure\n");
ppupdate = __of_prop_dup(&pupdate, GFP_KERNEL);
selftest(ppupdate, "testcase setup failure\n");
parent = nremove->parent;
n1->parent = parent;
n2->parent = parent;
n21->parent = n2;
n2->child = n21;
ppremove = of_find_property(parent, "prop-remove", NULL);
selftest(ppremove, "failed to find removal prop");
of_changeset_init(&chgset);
selftest(!of_changeset_attach_node(&chgset, n1), "fail attach n1\n");
selftest(!of_changeset_attach_node(&chgset, n2), "fail attach n2\n");
selftest(!of_changeset_detach_node(&chgset, nremove), "fail remove node\n");
selftest(!of_changeset_attach_node(&chgset, n21), "fail attach n21\n");
selftest(!of_changeset_add_property(&chgset, parent, ppadd), "fail add prop\n");
selftest(!of_changeset_update_property(&chgset, parent, ppupdate), "fail update prop\n");
selftest(!of_changeset_remove_property(&chgset, parent, ppremove), "fail remove prop\n");
mutex_lock(&of_mutex);
selftest(!of_changeset_apply(&chgset), "apply failed\n");
mutex_unlock(&of_mutex);
mutex_lock(&of_mutex);
selftest(!of_changeset_revert(&chgset), "revert failed\n");
mutex_unlock(&of_mutex);
of_changeset_destroy(&chgset);
#endif
}
static void __init of_selftest_parse_interrupts(void)
{
struct device_node *np;
@ -561,6 +611,7 @@ static int __init of_selftest(void)
of_selftest_parse_phandle_with_args();
of_selftest_property_match_string();
of_selftest_property_copy();
of_selftest_changeset();
of_selftest_parse_interrupts();
of_selftest_parse_interrupts_extended();
of_selftest_match_node();

View file

@ -1,3 +1,13 @@
/ {
testcase-data {
changeset {
prop-update = "hello";
prop-remove = "world";
node-remove {
};
};
};
};
#include "tests-phandle.dtsi"
#include "tests-interrupts.dtsi"
#include "tests-match.dtsi"