diff options
author | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2021-06-17 15:57:07 +0200 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2021-06-17 15:57:07 +0200 |
commit | 5f4ce26078fde9cd406c008ba35e31bbb26a23a1 (patch) | |
tree | 02b429ff48fc0b3518058f8d011239f2ac3c4e0b /drivers/acpi | |
parent | c6a493a1b603ed216ce69d1faac3f0ddc6a2f8eb (diff) | |
download | linux-5f4ce26078fde9cd406c008ba35e31bbb26a23a1.tar.bz2 |
ACPI: scan: Fix race related to dropping dependencies
If acpi_add_single_object() runs concurrently with respect to
acpi_scan_clear_dep() which deletes a dependencies list entry where
the device being added is the consumer, the device's dep_unmet
counter may not be updated to reflect that change.
Namely, if the dependencies list entry is deleted right after
calling acpi_scan_dep_init() and before calling acpi_device_add(),
acpi_scan_clear_dep() will not find the device object corresponding
to the consumer device ACPI handle and it will not update its
dep_unmet counter to reflect the deletion of the list entry.
Consequently, the dep_unmet counter of the device will never
become zero going forward which may prevent it from being
completely enumerated.
To address this problem, modify acpi_add_single_object() to run
acpi_tie_acpi_dev(), to attach the ACPI device object created by it
to the corresponding ACPI namespace node, under acpi_dep_list_lock
along with acpi_scan_dep_init() whenever the latter is called.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Diffstat (limited to 'drivers/acpi')
-rw-r--r-- | drivers/acpi/scan.c | 45 |
1 files changed, 32 insertions, 13 deletions
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index c62ce287fdb9..1c6205661000 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -650,16 +650,12 @@ static int acpi_tie_acpi_dev(struct acpi_device *adev) return 0; } -int acpi_device_add(struct acpi_device *device, - void (*release)(struct device *)) +static int __acpi_device_add(struct acpi_device *device, + void (*release)(struct device *)) { struct acpi_device_bus_id *acpi_device_bus_id; int result; - result = acpi_tie_acpi_dev(device); - if (result) - return result; - /* * Linkage * ------- @@ -748,6 +744,17 @@ err_unlock: return result; } +int acpi_device_add(struct acpi_device *adev, void (*release)(struct device *)) +{ + int ret; + + ret = acpi_tie_acpi_dev(adev); + if (ret) + return ret; + + return __acpi_device_add(adev, release); +} + /* -------------------------------------------------------------------------- Device Enumeration -------------------------------------------------------------------------- */ @@ -1675,14 +1682,10 @@ static void acpi_scan_dep_init(struct acpi_device *adev) { struct acpi_dep_data *dep; - mutex_lock(&acpi_dep_list_lock); - list_for_each_entry(dep, &acpi_dep_list, node) { if (dep->consumer == adev->handle) adev->dep_unmet++; } - - mutex_unlock(&acpi_dep_list_lock); } void acpi_device_add_finalize(struct acpi_device *device) @@ -1701,6 +1704,7 @@ static int acpi_add_single_object(struct acpi_device **child, acpi_handle handle, int type, bool dep_init) { struct acpi_device *device; + bool release_dep_lock = false; int result; device = kzalloc(sizeof(struct acpi_device), GFP_KERNEL); @@ -1714,16 +1718,31 @@ static int acpi_add_single_object(struct acpi_device **child, * this must be done before the get power-/wakeup_dev-flags calls. */ if (type == ACPI_BUS_TYPE_DEVICE || type == ACPI_BUS_TYPE_PROCESSOR) { - if (dep_init) + if (dep_init) { + mutex_lock(&acpi_dep_list_lock); + /* + * Hold the lock until the acpi_tie_acpi_dev() call + * below to prevent concurrent acpi_scan_clear_dep() + * from deleting a dependency list entry without + * updating dep_unmet for the device. + */ + release_dep_lock = true; acpi_scan_dep_init(device); - + } acpi_scan_init_status(device); } acpi_bus_get_power_flags(device); acpi_bus_get_wakeup_device_flags(device); - result = acpi_device_add(device, acpi_device_release); + result = acpi_tie_acpi_dev(device); + + if (release_dep_lock) + mutex_unlock(&acpi_dep_list_lock); + + if (!result) + result = __acpi_device_add(device, acpi_device_release); + if (result) { acpi_device_release(&device->dev); return result; |