From: "Rafael J. Wysocki" <rafael@kernel.org>
To: Hamish Martin <hamish.martin@alliedtelesis.co.nz>
Cc: mika.westerberg@linux.intel.com,
andriy.shevchenko@linux.intel.com, linus.walleij@linaro.org,
brgl@bgdev.pl, linux-gpio@vger.kernel.org,
linux-acpi@vger.kernel.org
Subject: Re: [PATCH] gpiolib: acpi: Move storage of acpi_gpio_chip
Date: Thu, 18 Apr 2024 21:29:31 +0200 [thread overview]
Message-ID: <CAJZ5v0jChQdAUaYCPoiao1fhxt90A3auybEW72DRXEwCxE3=Pg@mail.gmail.com> (raw)
In-Reply-To: <20240313030251.1049624-1-hamish.martin@alliedtelesis.co.nz>
On Wed, Mar 13, 2024 at 4:03 AM Hamish Martin
<hamish.martin@alliedtelesis.co.nz> wrote:
>
> A memory leak occurs in a scenario where an ACPI SSDT overlay is removed
> and that is the trigger for the removal of the acpi_gpio_chip.
> This occurs because we use the ACPI_HANDLE of the chip->parent as a
> convenient location to tie the 'struct acpi_gpio_chip' to, using
> acpi_attach_data().
> This is fine in the usual case of removal of the 'struct acpi_gpio_chip'
> via a call to acpi_gpiochip_remove() because usually the ACPI data is
> still valid.
> But in the case of an SSDT overlay removal, the ACPI data has been
> marked as invalid before the removal code is triggered (see the setting
> of the acpi_device handle to INVALID_ACPI_HANDLE in
> acpi_scan_drop_device())
This is not entirely accurate.
The ACPI handle of the struct acpi_device going away is replaced with
INVALID_ACPI_HANDLE because by the time acpi_device_del_work_fn()
runs, the namespace object identified by this handle may have been
deleted already.
Moreover, all of the data items attached to that object are deleted by
the very acpi_ns_delete_node() call that has invoked
acpi_scan_drop_device().
So acpi_scan_drop_device() must invalidate the ACPI handle in the
struct acpi_device, because going forward it is not associated with a
valid namespace object and all of the data items attached to it via
acpi_attach_data() have been deleted.
> This means that by the time we execute
> acpi_gpiochip_remove(), the handles are invalid and we are unable to
> retrieve the 'struct acpi_gpio_chip' using acpi_get_data().
Indeed, that information has gone away already.
However, acpi_gpio_chip_dh() is called when that happens, so in
principle you can know when it is happening.
> Unable to get our data, we hit the failure case and see the following warning
> logged: Failed to retrieve ACPI GPIO chip
> This means we also fail to kfree() the struct at the end of
> acpi_gpiochip_remove().
>
> The fix is to no longer tie the acpi_gpio_chip data to the ACPI data,
> but instead hang it directly from the 'struct gpio_chip' with a new
> field. This decouples the lifecycle of the acpi_gpio_chip from the ACPI
> data, and ties it to the gpio_chip. This seems a much better place since
> they share a common lifecycle.
I can agree with this.
> The general concept of attaching data to the ACPI objects may also need
> revisiting in light of the issue this patch addresses. For instance
> i2c_acpi_remove_space_handler() is vulnerable to a similar leak due to
> using acpi_bus_get_private_data(), which just wraps acpi_attach_data().
> This may need a more widespread change than is addressed in this patch.
acpi_bus_attach_private_data() is only used in 2 places beyond this,
so I'm not worried too much.
But yes, generally speaking, if the ACPI namespace object you attach
data to can vanish from under you as a result of an overlay removal,
you better not attach data to it or use a meaningful data removal
handler.
> Signed-off-by: Hamish Martin <hamish.martin@alliedtelesis.co.nz>
> ---
> drivers/gpio/gpiolib-acpi.c | 57 ++++++++-----------------------------
> include/linux/gpio/driver.h | 4 +++
> 2 files changed, 16 insertions(+), 45 deletions(-)
>
> diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c
> index cd3e9657cc36..14d29902391f 100644
> --- a/drivers/gpio/gpiolib-acpi.c
> +++ b/drivers/gpio/gpiolib-acpi.c
> @@ -180,11 +180,6 @@ static irqreturn_t acpi_gpio_irq_handler_evt(int irq, void *data)
> return IRQ_HANDLED;
> }
>
> -static void acpi_gpio_chip_dh(acpi_handle handle, void *data)
> -{
> - /* The address of this function is used as a key. */
> -}
> -
> bool acpi_gpio_get_irq_resource(struct acpi_resource *ares,
> struct acpi_resource_gpio **agpio)
> {
> @@ -500,18 +495,17 @@ void acpi_gpiochip_request_interrupts(struct gpio_chip *chip)
> {
> struct acpi_gpio_chip *acpi_gpio;
> acpi_handle handle;
> - acpi_status status;
> bool defer;
>
> if (!chip->parent || !chip->to_irq)
> return;
>
> - handle = ACPI_HANDLE(chip->parent);
> - if (!handle)
> + acpi_gpio = chip->acpi_gpio;
> + if (!acpi_gpio)
> return;
>
> - status = acpi_get_data(handle, acpi_gpio_chip_dh, (void **)&acpi_gpio);
> - if (ACPI_FAILURE(status))
> + handle = ACPI_HANDLE(chip->parent);
> + if (!handle)
> return;
>
> if (acpi_quirk_skip_gpio_event_handlers())
> @@ -545,18 +539,12 @@ void acpi_gpiochip_free_interrupts(struct gpio_chip *chip)
> {
> struct acpi_gpio_chip *acpi_gpio;
> struct acpi_gpio_event *event, *ep;
> - acpi_handle handle;
> - acpi_status status;
>
> if (!chip->parent || !chip->to_irq)
> return;
>
> - handle = ACPI_HANDLE(chip->parent);
> - if (!handle)
> - return;
> -
> - status = acpi_get_data(handle, acpi_gpio_chip_dh, (void **)&acpi_gpio);
> - if (ACPI_FAILURE(status))
> + acpi_gpio = chip->acpi_gpio;
> + if (!acpi_gpio)
> return;
>
> mutex_lock(&acpi_gpio_deferred_req_irqs_lock);
> @@ -1218,15 +1206,10 @@ static void acpi_gpiochip_free_regions(struct acpi_gpio_chip *achip)
> struct gpio_chip *chip = achip->chip;
> acpi_handle handle = ACPI_HANDLE(chip->parent);
> struct acpi_gpio_connection *conn, *tmp;
> - acpi_status status;
>
> - status = acpi_remove_address_space_handler(handle, ACPI_ADR_SPACE_GPIO,
> - acpi_gpio_adr_space_handler);
> - if (ACPI_FAILURE(status)) {
> - dev_err(chip->parent,
> - "Failed to remove GPIO OpRegion handler\n");
> - return;
> - }
> + /* Ignore the return status as the handle is likely no longer valid. */
> + acpi_remove_address_space_handler(handle, ACPI_ADR_SPACE_GPIO,
> + acpi_gpio_adr_space_handler);
>
> list_for_each_entry_safe_reverse(conn, tmp, &achip->conns, node) {
> gpiochip_free_own_desc(conn->desc);
> @@ -1310,7 +1293,6 @@ void acpi_gpiochip_add(struct gpio_chip *chip)
> {
> struct acpi_gpio_chip *acpi_gpio;
> struct acpi_device *adev;
> - acpi_status status;
>
> if (!chip || !chip->parent)
> return;
> @@ -1327,16 +1309,10 @@ void acpi_gpiochip_add(struct gpio_chip *chip)
> }
>
> acpi_gpio->chip = chip;
> + chip->acpi_gpio = acpi_gpio;
> INIT_LIST_HEAD(&acpi_gpio->events);
> INIT_LIST_HEAD(&acpi_gpio->deferred_req_irqs_list_entry);
>
> - status = acpi_attach_data(adev->handle, acpi_gpio_chip_dh, acpi_gpio);
> - if (ACPI_FAILURE(status)) {
> - dev_err(chip->parent, "Failed to attach ACPI GPIO chip\n");
> - kfree(acpi_gpio);
> - return;
> - }
> -
> acpi_gpiochip_request_regions(acpi_gpio);
> acpi_gpiochip_scan_gpios(acpi_gpio);
> acpi_dev_clear_dependencies(adev);
> @@ -1345,25 +1321,16 @@ void acpi_gpiochip_add(struct gpio_chip *chip)
> void acpi_gpiochip_remove(struct gpio_chip *chip)
> {
> struct acpi_gpio_chip *acpi_gpio;
> - acpi_handle handle;
> - acpi_status status;
>
> if (!chip || !chip->parent)
> return;
>
> - handle = ACPI_HANDLE(chip->parent);
> - if (!handle)
> + acpi_gpio = chip->acpi_gpio;
> + if (!acpi_gpio)
> return;
>
> - status = acpi_get_data(handle, acpi_gpio_chip_dh, (void **)&acpi_gpio);
> - if (ACPI_FAILURE(status)) {
> - dev_warn(chip->parent, "Failed to retrieve ACPI GPIO chip\n");
> - return;
> - }
> -
> acpi_gpiochip_free_regions(acpi_gpio);
>
> - acpi_detach_data(handle, acpi_gpio_chip_dh);
> kfree(acpi_gpio);
> }
>
> diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h
> index 7f75c9a51874..698b92b1764c 100644
> --- a/include/linux/gpio/driver.h
> +++ b/include/linux/gpio/driver.h
> @@ -529,6 +529,10 @@ struct gpio_chip {
> int (*of_xlate)(struct gpio_chip *gc,
> const struct of_phandle_args *gpiospec, u32 *flags);
> #endif /* CONFIG_OF_GPIO */
> +
> +#ifdef CONFIG_GPIO_ACPI
> + struct acpi_gpio_chip *acpi_gpio;
> +#endif /* CONFIG_GPIO_ACPI */
> };
>
> char *gpiochip_dup_line_label(struct gpio_chip *gc, unsigned int offset);
> --
> 2.43.2
>
>
prev parent reply other threads:[~2024-04-18 19:29 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-03-13 3:02 [PATCH] gpiolib: acpi: Move storage of acpi_gpio_chip Hamish Martin
2024-03-13 11:21 ` Andy Shevchenko
2024-03-13 11:26 ` Andy Shevchenko
2024-03-14 1:13 ` Hamish Martin
2024-03-14 13:18 ` andriy.shevchenko
2024-03-15 0:39 ` Hamish Martin
2024-03-18 10:52 ` andriy.shevchenko
2024-04-18 19:29 ` Rafael J. Wysocki [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='CAJZ5v0jChQdAUaYCPoiao1fhxt90A3auybEW72DRXEwCxE3=Pg@mail.gmail.com' \
--to=rafael@kernel.org \
--cc=andriy.shevchenko@linux.intel.com \
--cc=brgl@bgdev.pl \
--cc=hamish.martin@alliedtelesis.co.nz \
--cc=linus.walleij@linaro.org \
--cc=linux-acpi@vger.kernel.org \
--cc=linux-gpio@vger.kernel.org \
--cc=mika.westerberg@linux.intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).