Commit 10527106 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'dt-for-linus' of git://git.secretlab.ca/git/linux

Pull devicetree fixes from Grant Likely:
 "Device tree compatible match order bug fix

  This branch contains a bug fix for the way devicetree code identifies
  the type of device.  Device drivers can contain a list of
  of_device_ids, but it more than one entry will match, then the device
  driver may choose the wrong one.  Commit 10535314, "match each node
  compatible against all given matches first", was queued for v3.14 but
  ended up causing other bugs.  Commit 06b29e76 attempted to fix it
  but it had other bugs.  Merely reverting the fix and waiting until
  v3.15 isn't a good option because there is code in v3.14 that depends
  on the revised behaviour to boot.

  This branch should finally fixes the problem correctly.  This time
  instead of just hoping that the patch is correct, this branch also
  adds new testcases that validate the behaviour.

  The changes in this branch are larger than I would like for a -rc
  pull, but moving the test case data out of out of arch/arm so that it
  could be validated on other architectures was important"

* tag 'dt-for-linus' of git://git.secretlab.ca/git/linux:
  of: Add self test for of_match_node()
  of: Move testcase FDT data into drivers/of
  of: reimplement the matching method for __of_match_node()
  Revert "of: search the best compatible match first in __of_match_node()"
parents 0a4e836a 1f42e5dd
/include/ "tests-phandle.dtsi"
/include/ "tests-interrupts.dtsi"
/include/ "versatile-ab.dts" #include <versatile-ab.dts>
/ { / {
model = "ARM Versatile PB"; model = "ARM Versatile PB";
...@@ -47,4 +47,4 @@ mmc@b000 { ...@@ -47,4 +47,4 @@ mmc@b000 {
}; };
}; };
/include/ "testcases/tests.dtsi" #include <testcases.dtsi>
...@@ -342,27 +342,72 @@ struct device_node *of_get_cpu_node(int cpu, unsigned int *thread) ...@@ -342,27 +342,72 @@ struct device_node *of_get_cpu_node(int cpu, unsigned int *thread)
} }
EXPORT_SYMBOL(of_get_cpu_node); EXPORT_SYMBOL(of_get_cpu_node);
/** Checks if the given "compat" string matches one of the strings in /**
* the device's "compatible" property * __of_device_is_compatible() - Check if the node matches given constraints
* @device: pointer to node
* @compat: required compatible string, NULL or "" for any match
* @type: required device_type value, NULL or "" for any match
* @name: required node name, NULL or "" for any match
*
* Checks if the given @compat, @type and @name strings match the
* properties of the given @device. A constraints can be skipped by
* passing NULL or an empty string as the constraint.
*
* Returns 0 for no match, and a positive integer on match. The return
* value is a relative score with larger values indicating better
* matches. The score is weighted for the most specific compatible value
* to get the highest score. Matching type is next, followed by matching
* name. Practically speaking, this results in the following priority
* order for matches:
*
* 1. specific compatible && type && name
* 2. specific compatible && type
* 3. specific compatible && name
* 4. specific compatible
* 5. general compatible && type && name
* 6. general compatible && type
* 7. general compatible && name
* 8. general compatible
* 9. type && name
* 10. type
* 11. name
*/ */
static int __of_device_is_compatible(const struct device_node *device, static int __of_device_is_compatible(const struct device_node *device,
const char *compat) const char *compat, const char *type, const char *name)
{ {
const char* cp; struct property *prop;
int cplen, l; const char *cp;
int index = 0, score = 0;
/* Compatible match has highest priority */
if (compat && compat[0]) {
prop = __of_find_property(device, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
score = INT_MAX/2 - (index << 2);
break;
}
}
if (!score)
return 0;
}
cp = __of_get_property(device, "compatible", &cplen); /* Matching type is better than matching name */
if (cp == NULL) if (type && type[0]) {
if (!device->type || of_node_cmp(type, device->type))
return 0; return 0;
while (cplen > 0) { score += 2;
if (of_compat_cmp(cp, compat, strlen(compat)) == 0)
return 1;
l = strlen(cp) + 1;
cp += l;
cplen -= l;
} }
/* Matching name is a bit better than not */
if (name && name[0]) {
if (!device->name || of_node_cmp(name, device->name))
return 0; return 0;
score++;
}
return score;
} }
/** Checks if the given "compat" string matches one of the strings in /** Checks if the given "compat" string matches one of the strings in
...@@ -375,7 +420,7 @@ int of_device_is_compatible(const struct device_node *device, ...@@ -375,7 +420,7 @@ int of_device_is_compatible(const struct device_node *device,
int res; int res;
raw_spin_lock_irqsave(&devtree_lock, flags); raw_spin_lock_irqsave(&devtree_lock, flags);
res = __of_device_is_compatible(device, compat); res = __of_device_is_compatible(device, compat, NULL, NULL);
raw_spin_unlock_irqrestore(&devtree_lock, flags); raw_spin_unlock_irqrestore(&devtree_lock, flags);
return res; return res;
} }
...@@ -681,10 +726,7 @@ struct device_node *of_find_compatible_node(struct device_node *from, ...@@ -681,10 +726,7 @@ struct device_node *of_find_compatible_node(struct device_node *from,
raw_spin_lock_irqsave(&devtree_lock, flags); raw_spin_lock_irqsave(&devtree_lock, flags);
np = from ? from->allnext : of_allnodes; np = from ? from->allnext : of_allnodes;
for (; np; np = np->allnext) { for (; np; np = np->allnext) {
if (type if (__of_device_is_compatible(np, compatible, type, NULL) &&
&& !(np->type && (of_node_cmp(np->type, type) == 0)))
continue;
if (__of_device_is_compatible(np, compatible) &&
of_node_get(np)) of_node_get(np))
break; break;
} }
...@@ -730,65 +772,26 @@ struct device_node *of_find_node_with_property(struct device_node *from, ...@@ -730,65 +772,26 @@ struct device_node *of_find_node_with_property(struct device_node *from,
} }
EXPORT_SYMBOL(of_find_node_with_property); EXPORT_SYMBOL(of_find_node_with_property);
static const struct of_device_id *
of_match_compatible(const struct of_device_id *matches,
const struct device_node *node)
{
const char *cp;
int cplen, l;
const struct of_device_id *m;
cp = __of_get_property(node, "compatible", &cplen);
while (cp && (cplen > 0)) {
m = matches;
while (m->name[0] || m->type[0] || m->compatible[0]) {
/* Only match for the entries without type and name */
if (m->name[0] || m->type[0] ||
of_compat_cmp(m->compatible, cp,
strlen(m->compatible)))
m++;
else
return m;
}
/* Get node's next compatible string */
l = strlen(cp) + 1;
cp += l;
cplen -= l;
}
return NULL;
}
static static
const struct of_device_id *__of_match_node(const struct of_device_id *matches, const struct of_device_id *__of_match_node(const struct of_device_id *matches,
const struct device_node *node) const struct device_node *node)
{ {
const struct of_device_id *m; const struct of_device_id *best_match = NULL;
int score, best_score = 0;
if (!matches) if (!matches)
return NULL; return NULL;
m = of_match_compatible(matches, node); for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
if (m) score = __of_device_is_compatible(node, matches->compatible,
return m; matches->type, matches->name);
if (score > best_score) {
while (matches->name[0] || matches->type[0] || matches->compatible[0]) { best_match = matches;
int match = 1; best_score = score;
if (matches->name[0])
match &= node->name
&& !strcmp(matches->name, node->name);
if (matches->type[0])
match &= node->type
&& !strcmp(matches->type, node->type);
if (matches->compatible[0])
match &= __of_device_is_compatible(node,
matches->compatible);
if (match)
return matches;
matches++;
} }
return NULL; }
return best_match;
} }
/** /**
...@@ -796,12 +799,7 @@ const struct of_device_id *__of_match_node(const struct of_device_id *matches, ...@@ -796,12 +799,7 @@ const struct of_device_id *__of_match_node(const struct of_device_id *matches,
* @matches: array of of device match structures to search in * @matches: array of of device match structures to search in
* @node: the of device structure to match against * @node: the of device structure to match against
* *
* Low level utility function used by device matching. We have two ways * Low level utility function used by device matching.
* of matching:
* - Try to find the best compatible match by comparing each compatible
* string of device node with all the given matches respectively.
* - If the above method failed, then try to match the compatible by using
* __of_device_is_compatible() besides the match in type and name.
*/ */
const struct of_device_id *of_match_node(const struct of_device_id *matches, const struct of_device_id *of_match_node(const struct of_device_id *matches,
const struct device_node *node) const struct device_node *node)
......
...@@ -300,6 +300,72 @@ static void __init of_selftest_parse_interrupts_extended(void) ...@@ -300,6 +300,72 @@ static void __init of_selftest_parse_interrupts_extended(void)
of_node_put(np); of_node_put(np);
} }
static struct of_device_id match_node_table[] = {
{ .data = "A", .name = "name0", }, /* Name alone is lowest priority */
{ .data = "B", .type = "type1", }, /* followed by type alone */
{ .data = "Ca", .name = "name2", .type = "type1", }, /* followed by both together */
{ .data = "Cb", .name = "name2", }, /* Only match when type doesn't match */
{ .data = "Cc", .name = "name2", .type = "type2", },
{ .data = "E", .compatible = "compat3" },
{ .data = "G", .compatible = "compat2", },
{ .data = "H", .compatible = "compat2", .name = "name5", },
{ .data = "I", .compatible = "compat2", .type = "type1", },
{ .data = "J", .compatible = "compat2", .type = "type1", .name = "name8", },
{ .data = "K", .compatible = "compat2", .name = "name9", },
{}
};
static struct {
const char *path;
const char *data;
} match_node_tests[] = {
{ .path = "/testcase-data/match-node/name0", .data = "A", },
{ .path = "/testcase-data/match-node/name1", .data = "B", },
{ .path = "/testcase-data/match-node/a/name2", .data = "Ca", },
{ .path = "/testcase-data/match-node/b/name2", .data = "Cb", },
{ .path = "/testcase-data/match-node/c/name2", .data = "Cc", },
{ .path = "/testcase-data/match-node/name3", .data = "E", },
{ .path = "/testcase-data/match-node/name4", .data = "G", },
{ .path = "/testcase-data/match-node/name5", .data = "H", },
{ .path = "/testcase-data/match-node/name6", .data = "G", },
{ .path = "/testcase-data/match-node/name7", .data = "I", },
{ .path = "/testcase-data/match-node/name8", .data = "J", },
{ .path = "/testcase-data/match-node/name9", .data = "K", },
};
static void __init of_selftest_match_node(void)
{
struct device_node *np;
const struct of_device_id *match;
int i;
for (i = 0; i < ARRAY_SIZE(match_node_tests); i++) {
np = of_find_node_by_path(match_node_tests[i].path);
if (!np) {
selftest(0, "missing testcase node %s\n",
match_node_tests[i].path);
continue;
}
match = of_match_node(match_node_table, np);
if (!match) {
selftest(0, "%s didn't match anything\n",
match_node_tests[i].path);
continue;
}
if (strcmp(match->data, match_node_tests[i].data) != 0) {
selftest(0, "%s got wrong match. expected %s, got %s\n",
match_node_tests[i].path, match_node_tests[i].data,
(const char *)match->data);
continue;
}
selftest(1, "passed");
}
}
static int __init of_selftest(void) static int __init of_selftest(void)
{ {
struct device_node *np; struct device_node *np;
...@@ -316,6 +382,7 @@ static int __init of_selftest(void) ...@@ -316,6 +382,7 @@ static int __init of_selftest(void)
of_selftest_property_match_string(); of_selftest_property_match_string();
of_selftest_parse_interrupts(); of_selftest_parse_interrupts();
of_selftest_parse_interrupts_extended(); of_selftest_parse_interrupts_extended();
of_selftest_match_node();
pr_info("end of selftest - %i passed, %i failed\n", pr_info("end of selftest - %i passed, %i failed\n",
selftest_results.passed, selftest_results.failed); selftest_results.passed, selftest_results.failed);
return 0; return 0;
......
#include "tests-phandle.dtsi"
#include "tests-interrupts.dtsi"
#include "tests-match.dtsi"
/ {
testcase-data {
match-node {
name0 { };
name1 { device_type = "type1"; };
a { name2 { device_type = "type1"; }; };
b { name2 { }; };
c { name2 { device_type = "type2"; }; };
name3 { compatible = "compat3"; };
name4 { compatible = "compat2", "compat3"; };
name5 { compatible = "compat2", "compat3"; };
name6 { compatible = "compat1", "compat2", "compat3"; };
name7 { compatible = "compat2"; device_type = "type1"; };
name8 { compatible = "compat2"; device_type = "type1"; };
name9 { compatible = "compat2"; };
};
};
};
...@@ -152,6 +152,7 @@ ld_flags = $(LDFLAGS) $(ldflags-y) ...@@ -152,6 +152,7 @@ ld_flags = $(LDFLAGS) $(ldflags-y)
dtc_cpp_flags = -Wp,-MD,$(depfile).pre.tmp -nostdinc \ dtc_cpp_flags = -Wp,-MD,$(depfile).pre.tmp -nostdinc \
-I$(srctree)/arch/$(SRCARCH)/boot/dts \ -I$(srctree)/arch/$(SRCARCH)/boot/dts \
-I$(srctree)/arch/$(SRCARCH)/boot/dts/include \ -I$(srctree)/arch/$(SRCARCH)/boot/dts/include \
-I$(srctree)/drivers/of/testcase-data \
-undef -D__DTS__ -undef -D__DTS__
# Finds the multi-part object the current object will be linked into # Finds the multi-part object the current object will be linked into
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment