diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index 8d769114a048..552d82dfa476 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -105,51 +105,74 @@ static int find_child_checks(struct acpi_device *adev, bool check_children) return FIND_CHILD_MAX_SCORE; } +struct find_child_walk_data { + struct acpi_device *adev; + u64 address; + int score; + bool check_children; +}; + +static int check_one_child(struct acpi_device *adev, void *data) +{ + struct find_child_walk_data *wd = data; + int score; + + if (!adev->pnp.type.bus_address || acpi_device_adr(adev) != wd->address) + return 0; + + if (!wd->adev) { + /* This is the first matching object. Save it and continue. */ + wd->adev = adev; + return 0; + } + + /* + * There is more than one matching device object with the same _ADR + * value. That really is unexpected, so we are kind of beyond the scope + * of the spec here. We have to choose which one to return, though. + * + * First, get the score for the previously found object and terminate + * the walk if it is maximum. + */ + if (!wd->score) { + score = find_child_checks(wd->adev, wd->check_children); + if (score == FIND_CHILD_MAX_SCORE) + return 1; + + wd->score = score; + } + /* + * Second, if the object that has just been found has a better score, + * replace the previously found one with it and terminate the walk if + * the new score is maximum. + */ + score = find_child_checks(adev, wd->check_children); + if (score > wd->score) { + wd->adev = adev; + if (score == FIND_CHILD_MAX_SCORE) + return 1; + + wd->score = score; + } + + /* Continue, because there may be better matches. */ + return 0; +} + struct acpi_device *acpi_find_child_device(struct acpi_device *parent, u64 address, bool check_children) { - struct acpi_device *adev, *ret = NULL; - int ret_score = 0; + struct find_child_walk_data wd = { + .address = address, + .check_children = check_children, + .adev = NULL, + .score = 0, + }; - if (!parent) - return NULL; + if (parent) + acpi_dev_for_each_child(parent, check_one_child, &wd); - list_for_each_entry(adev, &parent->children, node) { - acpi_bus_address addr = acpi_device_adr(adev); - int score; - - if (!adev->pnp.type.bus_address || addr != address) - continue; - - if (!ret) { - /* This is the first matching object. Save it. */ - ret = adev; - continue; - } - /* - * There is more than one matching device object with the same - * _ADR value. That really is unexpected, so we are kind of - * beyond the scope of the spec here. We have to choose which - * one to return, though. - * - * First, check if the previously found object is good enough - * and return it if so. Second, do the same for the object that - * we've just found. - */ - if (!ret_score) { - ret_score = find_child_checks(ret, check_children); - if (ret_score == FIND_CHILD_MAX_SCORE) - return ret; - } - score = find_child_checks(adev, check_children); - if (score == FIND_CHILD_MAX_SCORE) { - return adev; - } else if (score > ret_score) { - ret = adev; - ret_score = score; - } - } - return ret; + return wd.adev; } EXPORT_SYMBOL_GPL(acpi_find_child_device);