ACPI / bind: Prefer device objects with _STA to those without it
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>
Mon, 9 Sep 2013 21:07:47 +0000 (23:07 +0200)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Mon, 9 Sep 2013 21:07:47 +0000 (23:07 +0200)
As reported at https://bugzilla.kernel.org/show_bug.cgi?id=60829,
there still are cases in which do_find_child() doesn't choose the
ACPI device object it is "expected" to choose if there are more such
objects matching one PCI device present.  This particular problem may
be worked around by making do_find_child() return device obejcts witn
_STA whose result indicates that the device is enabled before device
objects without _STA if there's more than one device object to choose
from.

This change doesn't affect the case in which there's only one
matching ACPI device object per PCI device.

References: https://bugzilla.kernel.org/show_bug.cgi?id=60829
Reported-by: Peter Wu <lekensteyn@gmail.com>
Tested-by: Felix Lisczyk <felix.lisczyk@gmail.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/acpi/glue.c

index 9467229..10f0f40 100644 (file)
@@ -79,6 +79,9 @@ static struct acpi_bus_type *acpi_get_bus_type(struct device *dev)
        return ret;
 }
 
+#define FIND_CHILD_MIN_SCORE   1
+#define FIND_CHILD_MAX_SCORE   2
+
 static acpi_status acpi_dev_present(acpi_handle handle, u32 lvl_not_used,
                                  void *not_used, void **ret_p)
 {
@@ -92,14 +95,17 @@ static acpi_status acpi_dev_present(acpi_handle handle, u32 lvl_not_used,
        return AE_OK;
 }
 
-static bool acpi_extra_checks_passed(acpi_handle handle, bool is_bridge)
+static int do_find_child_checks(acpi_handle handle, bool is_bridge)
 {
+       bool sta_present = true;
        unsigned long long sta;
        acpi_status status;
 
-       status = acpi_bus_get_status_handle(handle, &sta);
-       if (ACPI_FAILURE(status) || !(sta & ACPI_STA_DEVICE_ENABLED))
-               return false;
+       status = acpi_evaluate_integer(handle, "_STA", NULL, &sta);
+       if (status == AE_NOT_FOUND)
+               sta_present = false;
+       else if (ACPI_FAILURE(status) || !(sta & ACPI_STA_DEVICE_ENABLED))
+               return -ENODEV;
 
        if (is_bridge) {
                void *test = NULL;
@@ -107,16 +113,17 @@ static bool acpi_extra_checks_passed(acpi_handle handle, bool is_bridge)
                /* Check if this object has at least one child device. */
                acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1,
                                    acpi_dev_present, NULL, NULL, &test);
-               return !!test;
+               if (!test)
+                       return -ENODEV;
        }
-       return true;
+       return sta_present ? FIND_CHILD_MAX_SCORE : FIND_CHILD_MIN_SCORE;
 }
 
 struct find_child_context {
        u64 addr;
        bool is_bridge;
        acpi_handle ret;
-       bool ret_checked;
+       int ret_score;
 };
 
 static acpi_status do_find_child(acpi_handle handle, u32 lvl_not_used,
@@ -125,6 +132,7 @@ static acpi_status do_find_child(acpi_handle handle, u32 lvl_not_used,
        struct find_child_context *context = data;
        unsigned long long addr;
        acpi_status status;
+       int score;
 
        status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &addr);
        if (ACPI_FAILURE(status) || addr != context->addr)
@@ -144,15 +152,20 @@ static acpi_status do_find_child(acpi_handle handle, u32 lvl_not_used,
         * its handle if so.  Second, check the same for the object that we've
         * just found.
         */
-       if (!context->ret_checked) {
-               if (acpi_extra_checks_passed(context->ret, context->is_bridge))
+       if (!context->ret_score) {
+               score = do_find_child_checks(context->ret, context->is_bridge);
+               if (score == FIND_CHILD_MAX_SCORE)
                        return AE_CTRL_TERMINATE;
                else
-                       context->ret_checked = true;
+                       context->ret_score = score;
        }
-       if (acpi_extra_checks_passed(handle, context->is_bridge)) {
+       score = do_find_child_checks(handle, context->is_bridge);
+       if (score == FIND_CHILD_MAX_SCORE) {
                context->ret = handle;
                return AE_CTRL_TERMINATE;
+       } else if (score > context->ret_score) {
+               context->ret = handle;
+               context->ret_score = score;
        }
        return AE_OK;
 }