dm: core: Match compatible strings in order of priority
authorPaul Burton <paul.burton@imgtec.com>
Thu, 8 Sep 2016 06:47:36 +0000 (07:47 +0100)
committerDaniel Schwierzeck <daniel.schwierzeck@gmail.com>
Wed, 21 Sep 2016 13:04:32 +0000 (15:04 +0200)
Device model drivers have previously been matched to FDT nodes by virtue
of being the first driver in the driver list to be compatible with the
node. This ignores the fact that compatible strings in the device tree
are listed in order of priority - that is, if we have a node with 2
compatible strings & a driver that matches each then we should always
probe the driver that matches the first compatible string.

Fix this by looping through the compatible strings for a node when
attempting to bind it in lists_bind_fdt and checking each driver for
a match of the first string, then each driver for a match of the second
string etc. Effectively this inverts the loops over compatible strings &
drivers.

Signed-off-by: Paul Burton <paul.burton@imgtec.com>
drivers/core/lists.c

index 6a634e695180cd35b35619ee6bdb10dbfa31ec08..23b6ba78d3b0b1876cf8b2ab58731bbe384d7e68 100644 (file)
@@ -101,36 +101,24 @@ int device_bind_driver_to_node(struct udevice *parent, const char *drv_name,
 
 #if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
 /**
- * driver_check_compatible() - Check if a driver is compatible with this node
+ * driver_check_compatible() - Check if a driver matches a compatible string
  *
- * @param blob:                Device tree pointer
- * @param offset:      Offset of node in device tree
  * @param of_match:    List of compatible strings to match
  * @param of_idp:      Returns the match that was found
- * @return 0 if there is a match, -ENOENT if no match, -ENODEV if the node
- * does not have a compatible string, other error <0 if there is a device
- * tree error
+ * @param compat:      The compatible string to search for
+ * @return 0 if there is a match, -ENOENT if no match
  */
-static int driver_check_compatible(const void *blob, int offset,
-                                  const struct udevice_id *of_match,
-                                  const struct udevice_id **of_idp)
+static int driver_check_compatible(const struct udevice_id *of_match,
+                                  const struct udevice_id **of_idp,
+                                  const char *compat)
 {
-       int ret;
-
-       *of_idp = NULL;
        if (!of_match)
                return -ENOENT;
 
        while (of_match->compatible) {
-               ret = fdt_node_check_compatible(blob, offset,
-                                               of_match->compatible);
-               if (!ret) {
+               if (!strcmp(of_match->compatible, compat)) {
                        *of_idp = of_match;
                        return 0;
-               } else if (ret == -FDT_ERR_NOTFOUND) {
-                       return -ENODEV;
-               } else if (ret < 0) {
-                       return -EINVAL;
                }
                of_match++;
        }
@@ -147,28 +135,46 @@ int lists_bind_fdt(struct udevice *parent, const void *blob, int offset,
        struct driver *entry;
        struct udevice *dev;
        bool found = false;
-       const char *name;
+       const char *name, *compat_list, *compat;
+       int compat_length, i;
        int result = 0;
        int ret = 0;
 
-       dm_dbg("bind node %s\n", fdt_get_name(blob, offset, NULL));
+       name = fdt_get_name(blob, offset, NULL);
+       dm_dbg("bind node %s\n", name);
        if (devp)
                *devp = NULL;
-       for (entry = driver; entry != driver + n_ents; entry++) {
-               ret = driver_check_compatible(blob, offset, entry->of_match,
-                                             &id);
-               name = fdt_get_name(blob, offset, NULL);
-               if (ret == -ENOENT) {
-                       continue;
-               } else if (ret == -ENODEV) {
+
+       compat_list = fdt_getprop(blob, offset, "compatible", &compat_length);
+       if (!compat_list) {
+               if (compat_length == -FDT_ERR_NOTFOUND) {
                        dm_dbg("Device '%s' has no compatible string\n", name);
-                       break;
-               } else if (ret) {
-                       dm_warn("Device tree error at offset %d\n", offset);
-                       result = ret;
-                       break;
+                       return 0;
                }
 
+               dm_warn("Device tree error at offset %d\n", offset);
+               return compat_length;
+       }
+
+       /*
+        * Walk through the compatible string list, attempting to match each
+        * compatible string in order such that we match in order of priority
+        * from the first string to the last.
+        */
+       for (i = 0; i < compat_length; i += strlen(compat) + 1) {
+               compat = compat_list + i;
+               dm_dbg("   - attempt to match compatible string '%s'\n",
+                      compat);
+
+               for (entry = driver; entry != driver + n_ents; entry++) {
+                       ret = driver_check_compatible(entry->of_match, &id,
+                                                     compat);
+                       if (!ret)
+                               break;
+               }
+               if (entry == driver + n_ents)
+                       continue;
+
                dm_dbg("   - found match at '%s'\n", entry->name);
                ret = device_bind_with_driver_data(parent, entry, name,
                                                   id->data, offset, &dev);
@@ -188,10 +194,8 @@ int lists_bind_fdt(struct udevice *parent, const void *blob, int offset,
                break;
        }
 
-       if (!found && !result && ret != -ENODEV) {
-               dm_dbg("No match for node '%s'\n",
-                      fdt_get_name(blob, offset, NULL));
-       }
+       if (!found && !result && ret != -ENODEV)
+               dm_dbg("No match for node '%s'\n", name);
 
        return result;
 }