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. Commit105353145e
, "match each node compatible against all given matches first", was queued for v3.14 but ended up causing other bugs. Commit06b29e76a7
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. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.14 (GNU/Linux) iQIcBAABAgAGBQJTB2qsAAoJEMWQL496c2LNmvYQAJPwmc3f76L/HxxuJZ3TVpFF 2JhtCUyzTrsuebDSanAZLhzwCrIL4N8I/1rZObvkQuxUEUPNg4Khx9chMYwsG7L7 vbdr+3bkYnwaaGHzoH0NnNM5x1NLm29WOeHaWvT9Nm8iA1399AaOeAXniLQrTgBv LxipQc3SZpRSKdCDCi2y32TESMJcN7FT1aD4EKHAZocmXMpMaxEYQ2eFO70Txf5O 1SbNkZ7LsAnm1TmoH7nf0um9IFX7bvt2KkonpKBL2mOoDfq40pBosV3Zt2JwaeDy 3nkBeAFs3YvIDjZy3h+WdWLXlF9E29P7CXreE/uSJTnoYft6aCDfgz15USc2j2OA BTnK0kqJ3NWu6YAKrQHHoQmisnuDwg84oEK0JLJCfMcA3IOCBXV+iYHbo41j/dO5 yprnS1zms6UZuOXV2RjseQ34THkR3oDPbSxKmFTK/KGaa568ES0l1ZPAsvuVZqNr 5elDskt+lfF96WLGbyC0DP5Zib/0eFTbf18p///4lefkr6ysT+CdIJTsRj0/Uz0V 40nwCJH2t+wVCtcI/+mo3yvc4C77OoRhAOAxz8D9YkOlt8ilfgIDeAc1krAAa09T 0KfpfkLDvXEccZF+Jo2Z9TU7QFdeLVK/QAsvCZN4EcMU31ePEldMiniZ69/aySPl D/ahG24w1pakKXujtJK2 =wJz0 -----END PGP SIGNATURE----- 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. Commit105353145e
, "match each node compatible against all given matches first", was queued for v3.14 but ended up causing other bugs. Commit06b29e76a7
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()"
This commit is contained in:
commit
10527106ab
9 changed files with 166 additions and 80 deletions
|
@ -1,2 +0,0 @@
|
|||
/include/ "tests-phandle.dtsi"
|
||||
/include/ "tests-interrupts.dtsi"
|
|
@ -1,4 +1,4 @@
|
|||
/include/ "versatile-ab.dts"
|
||||
#include <versatile-ab.dts>
|
||||
|
||||
/ {
|
||||
model = "ARM Versatile PB";
|
||||
|
@ -47,4 +47,4 @@
|
|||
};
|
||||
};
|
||||
|
||||
/include/ "testcases/tests.dtsi"
|
||||
#include <testcases.dtsi>
|
||||
|
|
|
@ -342,27 +342,72 @@ struct device_node *of_get_cpu_node(int cpu, unsigned int *thread)
|
|||
}
|
||||
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,
|
||||
const char *compat)
|
||||
const char *compat, const char *type, const char *name)
|
||||
{
|
||||
const char* cp;
|
||||
int cplen, l;
|
||||
struct property *prop;
|
||||
const char *cp;
|
||||
int index = 0, score = 0;
|
||||
|
||||
cp = __of_get_property(device, "compatible", &cplen);
|
||||
if (cp == NULL)
|
||||
return 0;
|
||||
while (cplen > 0) {
|
||||
if (of_compat_cmp(cp, compat, strlen(compat)) == 0)
|
||||
return 1;
|
||||
l = strlen(cp) + 1;
|
||||
cp += l;
|
||||
cplen -= l;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
/* Matching type is better than matching name */
|
||||
if (type && type[0]) {
|
||||
if (!device->type || of_node_cmp(type, device->type))
|
||||
return 0;
|
||||
score += 2;
|
||||
}
|
||||
|
||||
/* Matching name is a bit better than not */
|
||||
if (name && name[0]) {
|
||||
if (!device->name || of_node_cmp(name, device->name))
|
||||
return 0;
|
||||
score++;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/** 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,
|
|||
int res;
|
||||
|
||||
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);
|
||||
return res;
|
||||
}
|
||||
|
@ -681,10 +726,7 @@ struct device_node *of_find_compatible_node(struct device_node *from,
|
|||
raw_spin_lock_irqsave(&devtree_lock, flags);
|
||||
np = from ? from->allnext : of_allnodes;
|
||||
for (; np; np = np->allnext) {
|
||||
if (type
|
||||
&& !(np->type && (of_node_cmp(np->type, type) == 0)))
|
||||
continue;
|
||||
if (__of_device_is_compatible(np, compatible) &&
|
||||
if (__of_device_is_compatible(np, compatible, type, NULL) &&
|
||||
of_node_get(np))
|
||||
break;
|
||||
}
|
||||
|
@ -730,65 +772,26 @@ struct device_node *of_find_node_with_property(struct device_node *from,
|
|||
}
|
||||
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
|
||||
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
|
||||
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)
|
||||
return NULL;
|
||||
|
||||
m = of_match_compatible(matches, node);
|
||||
if (m)
|
||||
return m;
|
||||
|
||||
while (matches->name[0] || matches->type[0] || matches->compatible[0]) {
|
||||
int match = 1;
|
||||
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++;
|
||||
for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
|
||||
score = __of_device_is_compatible(node, matches->compatible,
|
||||
matches->type, matches->name);
|
||||
if (score > best_score) {
|
||||
best_match = matches;
|
||||
best_score = score;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
||||
return best_match;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
* @node: the of device structure to match against
|
||||
*
|
||||
* Low level utility function used by device matching. We have two ways
|
||||
* 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.
|
||||
* Low level utility function used by device matching.
|
||||
*/
|
||||
const struct of_device_id *of_match_node(const struct of_device_id *matches,
|
||||
const struct device_node *node)
|
||||
|
|
|
@ -300,6 +300,72 @@ static void __init of_selftest_parse_interrupts_extended(void)
|
|||
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)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
@ -316,6 +382,7 @@ static int __init of_selftest(void)
|
|||
of_selftest_property_match_string();
|
||||
of_selftest_parse_interrupts();
|
||||
of_selftest_parse_interrupts_extended();
|
||||
of_selftest_match_node();
|
||||
pr_info("end of selftest - %i passed, %i failed\n",
|
||||
selftest_results.passed, selftest_results.failed);
|
||||
return 0;
|
||||
|
|
3
drivers/of/testcase-data/testcases.dtsi
Normal file
3
drivers/of/testcase-data/testcases.dtsi
Normal file
|
@ -0,0 +1,3 @@
|
|||
#include "tests-phandle.dtsi"
|
||||
#include "tests-interrupts.dtsi"
|
||||
#include "tests-match.dtsi"
|
19
drivers/of/testcase-data/tests-match.dtsi
Normal file
19
drivers/of/testcase-data/tests-match.dtsi
Normal file
|
@ -0,0 +1,19 @@
|
|||
|
||||
/ {
|
||||
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)
|
|||
dtc_cpp_flags = -Wp,-MD,$(depfile).pre.tmp -nostdinc \
|
||||
-I$(srctree)/arch/$(SRCARCH)/boot/dts \
|
||||
-I$(srctree)/arch/$(SRCARCH)/boot/dts/include \
|
||||
-I$(srctree)/drivers/of/testcase-data \
|
||||
-undef -D__DTS__
|
||||
|
||||
# Finds the multi-part object the current object will be linked into
|
||||
|
|
Loading…
Reference in a new issue