Linux PCI subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Atharva Tiwari" <atharvatiwarilinuxdev@gmail.com>
Cc: "open list:PCI SUBSYSTEM" <linux-pci@vger.kernel.org>,
	"open list:DRM DRIVERS" <dri-devel@lists.freedesktop.org>
Subject: Re: [PATCH 04/13] platform/apple-gmux: Fix Interrupts for MMIO systems
Date: Sun, 25 Oct 2026 04:39:41 +0000	[thread overview]
Message-ID: <sashiko-outbox-184073@kernel.org> (raw)
In-Reply-To: <20261025040046.1602-5-atharvatiwarilinuxdev@gmail.com>

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] platform/apple-gmux: incorrect teardown order in gmux_remove() causes UAF
- [High] platform/apple-gmux: cancel_work_sync() on uninitialized work in gmux_remove()
- [High] platform/apple-gmux: self-deadlock on powerchange_done in gmux_notify_work()
- [High] platform/apple-gmux: iounmap() before cancel_work_sync() in gmux_probe()
- [High] platform/apple-gmux: missing interrupt_work cancellation in gmux_suspend()
- [High] platform/apple-gmux: unhandled status=0 GPEs and late init in gmux_probe()
- [High] platform/x86: apple-gmux: unreleased lock and uninit val in gmux_mmio_read8()
- [Medium] platform/apple-gmux: lost events and unbounded loop in gmux_notify_work()
--

commit 8b786eb41ee899344257c35e97176ec39dc10858
Author: Atharva Tiwari <atharvatiwarilinuxdev@gmail.com>

platform/apple-gmux: Fix Interrupts for MMIO systems

This commit moves MMIO GMUX interrupt handling to a workqueue item, checks
for failed interrupt status bits, and wakes the discrete GPU via runtime PM
when external display hotplug interrupts occur on MMIO systems.

> diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c
> index 076ed7ba6af90..bef87a57ad43c 100644
> --- a/drivers/platform/x86/apple-gmux.c
> +++ b/drivers/platform/x86/apple-gmux.c

[ ... ]

> @@ -825,23 +838,78 @@ static void gmux_clear_interrupts(struct apple_gmux_data *gmux_data)
>  	/* to clear interrupts write back current status */
>  	status = gmux_interrupt_get_status(gmux_data);
>  	gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_STATUS, status);
> -	/* Prevent flood of status=0 interrupts */
> -	if (gmux_data->config == &apple_gmux_mmio)
> -		acpi_execute_simple_method(gmux_data->dhandle, "GMSP", 0);
>  }
>  
> +static void gmux_notify_work(struct work_struct *work)
> +{
> +	struct apple_gmux_data *gmux_data =
> +		container_of(work, struct apple_gmux_data, interrupt_work);
> +	u8 status;
> +	bool turn_on_gpu = false;
> +
> +	status = gmux_interrupt_get_status(gmux_data);
> +	if (!status)
> +		return;
> +
> +	gmux_disable_interrupts(gmux_data);
> +	pr_debug("Notify handler called: status %d\n", status);
> +	acpi_execute_simple_method(gmux_data->dhandle, "GMSP", 0);

[Severity: High]
If a GPE interrupt fires on an MMIO GMUX system while
GMUX_PORT_INTERRUPT_STATUS reads 0, doesn't returning early before calling
GMSP(0) leave the status=0 interrupt flood uncleared?

Previously, gmux_clear_interrupts() called GMSP(0) even when status was 0.
In gmux_probe(), acpi_install_notify_handler() and acpi_enable_gpe() are
called before init_completion(&gmux_data->powerchange_done) and before
gmux_enable_interrupts(gmux_data, 1) initializes the GPIO polarity via
GMSP(0):

gmux_probe()
    acpi_install_notify_handler(..., &gmux_notify_handler, pnp);
    acpi_enable_gpe(NULL, gmux_data->gpe);
    ...
    init_completion(&gmux_data->powerchange_done);
    gmux_enable_interrupts(gmux_data, 1);

If a GPE fires in that window (or during resume before gmux_resume() runs)
with status == 0, gmux_notify_work() returns without calling GMSP(0). And if
a GPE fires in that probe window with GMUX_INTERRUPT_STATUS_POWER set, won't
gmux_notify_work() call complete(&gmux_data->powerchange_done) before
init_completion(&gmux_data->powerchange_done) has initialized the waitqueue?

[Severity: High]
What happens in gmux_notify_work() if gmux_mmio_wait() times out inside
gmux_mmio_read8()?

On MMIO systems, gmux_interrupt_get_status() and gmux_read8() call
gmux_mmio_read8() (introduced in the earlier series patch "platform/apple-gmux:
Fix MMIO interface"), which has the same structure as gmux_mmio_read32():

static u8 gmux_mmio_read8(struct apple_gmux_data *gmux_data, int port)
{
	u8 val;

	mutex_lock(&gmux_data->index_lock);
	if (gmux_mmio_wait(gmux_data)) {
		iowrite8((port & 0xff), gmux_data->iomem_base + GMUX_MMIO_PORT_SELECT);
		iowrite8(GMUX_MMIO_READ | sizeof(val),
			gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
		if (!gmux_mmio_wait(gmux_data))
			return 0;
		val = ioread8(gmux_data->iomem_base);
	}
	mutex_unlock(&gmux_data->index_lock);

	return val;
}

If the first gmux_mmio_wait() call fails, gmux_mmio_read8() (and
gmux_mmio_read32()) returns the uninitialized stack variable val, causing
gmux_notify_work() to treat stack garbage as the interrupt status and
potentially loop, complete gmux_data->powerchange_done, or wake the discrete
GPU.

If the second gmux_mmio_wait() call fails, gmux_mmio_read8() (and
gmux_mmio_read32()) returns 0 while still holding gmux_data->index_lock, so
the subsequent gmux_enable_interrupts() -> gmux_write8() -> gmux_mmio_write8()
call in gmux_notify_work() will immediately deadlock on gmux_data->index_lock.

> +
> +	status = gmux_interrupt_get_status(gmux_data);
> +	while (status) {
> +		gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_STATUS, status);
> +		if (status & GMUX_INTERRUPT_STATUS_FAILED) {
> +			pr_warn_ratelimited("interrupt failed: status: 0x%x\n", status);
> +			status = gmux_interrupt_get_status(gmux_data);
> +			continue;
> +		}

[Severity: Medium]
If GMUX_INTERRUPT_STATUS_FAILED is set in status at the same time as
GMUX_INTERRUPT_STATUS_POWER or GMUX_INTERRUPT_STATUS_HOTPLUG, doesn't writing
the full status value to GMUX_PORT_INTERRUPT_STATUS clear all of those bits in
hardware before the continue statement skips handling them?

Also, if clearing GMUX_PORT_INTERRUPT_STATUS fails (for example, if
gmux_mmio_wait() times out in gmux_mmio_write8() or a hardware status bit
remains stuck), could while (status) spin indefinitely on system_percpu_wq?

> +		if (status & GMUX_INTERRUPT_STATUS_POWER)
> +			complete(&gmux_data->powerchange_done);
> +
> +		if (status & GMUX_INTERRUPT_STATUS_HOTPLUG) {
> +			/*
> +			 * We get an interrupt every time a display connects or disconnects, but
> +			 * the GET_DISPLAY port only tells us if a display is connected or not,
> +			 * so we resume every time a display is connected or disconnected.
> +			 */
> +			bool external_state = !!gmux_read8(gmux_data,
> +						GMUX_PORT_SWITCH_GET_DISPLAY);
> +			if (external_state != gmux_data->external_state) {
> +				gmux_data->external_state = external_state;
> +				if (external_state)
> +					pr_debug("A display has been connected\n");
> +				else
> +					pr_debug("All displays has been disconnected\n");
> +			}
> +			if (!gmux_data->external_switchable && gmux_data->discrete_pdev)
> +				turn_on_gpu = true;
> +		}
> +		status = gmux_interrupt_get_status(gmux_data);
> +	}
> +
> +	gmux_enable_interrupts(gmux_data, 0);
> +
> +	if (turn_on_gpu)
> +		pm_runtime_resume(&gmux_data->discrete_pdev->dev);
> +
> +}

[Severity: High]
Won't calling synchronous pm_runtime_resume(&gmux_data->discrete_pdev->dev)
from inside gmux_notify_work() self-deadlock on gmux_data->powerchange_done
when the discrete GPU is runtime-suspended?

When discrete_pdev is runtime-suspended, pm_runtime_resume() synchronously
runs the power switch callback on the current worker thread:

gmux_notify_work() [executing gmux_data->interrupt_work]
  pm_runtime_resume(&gmux_data->discrete_pdev->dev)
    ...
      vga_switcheroo_runtime_resume()
        vga_switcheroo_power_switch()
          gmux_set_power_state()
            gmux_set_discrete_state(apple_gmux_data, VGA_SWITCHEROO_ON)
              reinit_completion(&gmux_data->powerchange_done);
              ...
              wait_for_completion_interruptible_timeout(&gmux_data->powerchange_done,
                                                        msecs_to_jiffies(200));

When the resulting GMUX_INTERRUPT_STATUS_POWER interrupt fires,
gmux_notify_handler() calls schedule_work(&gmux_data->interrupt_work).
Because gmux_data->interrupt_work is the same work_struct currently blocked in
pm_runtime_resume() and a work_struct cannot run concurrently with itself,
gmux_notify_work() cannot run to call complete(&gmux_data->powerchange_done)
until wait_for_completion_interruptible_timeout() times out after 200ms.

Would asynchronous pm_request_resume() avoid blocking interrupt_work here?

[ ... ]

> @@ -935,7 +1003,7 @@ static int gmux_resume(struct device *dev)
>  	struct pnp_dev *pnp = to_pnp_dev(dev);
>  	struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
>  
> -	gmux_enable_interrupts(gmux_data);
> +	gmux_enable_interrupts(gmux_data, 1);
>  	gmux_write_switch_state(gmux_data);
>  	if (gmux_data->power_state == VGA_SWITCHEROO_OFF)
>  		gmux_set_discrete_state(gmux_data, gmux_data->power_state);

[Severity: High]
In gmux_suspend() right above gmux_resume(), gmux_disable_interrupts() is
called without cancelling gmux_data->interrupt_work:

static int gmux_suspend(struct device *dev)
{
	struct pnp_dev *pnp = to_pnp_dev(dev);
	struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);

	gmux_disable_interrupts(gmux_data);
	return 0;
}

If gmux_data->interrupt_work is pending or running when gmux_suspend()
executes on an MMIO system, can gmux_notify_work() run concurrently with or
after gmux_suspend(), re-enabling interrupts via
gmux_enable_interrupts(gmux_data, 0) and calling
pm_runtime_resume(&gmux_data->discrete_pdev->dev) during system suspend?

[ ... ]

> @@ -1150,6 +1219,8 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
>  err_unmap:
>  	if (gmux_data->iomem_base)
>  		iounmap(gmux_data->iomem_base);
> +	if (gmux_data->type == APPLE_GMUX_TYPE_MMIO)
> +		cancel_work_sync(&gmux_data->interrupt_work);
>  err_release:
>  	if (gmux_data->config->resource_type == IORESOURCE_MEM)
>  		release_mem_region(gmux_data->iostart, gmux_data->iolen);

[Severity: High]
In the err_unmap error path of gmux_probe(), iounmap(gmux_data->iomem_base) is
called before cancel_work_sync(&gmux_data->interrupt_work). If
gmux_notify_work() was scheduled before gmux_probe() failed, won't
gmux_notify_work() access unmapped MMIO memory through gmux_data->iomem_base
before cancel_work_sync() waits for it to finish?

> @@ -1168,6 +1239,7 @@ static void gmux_remove(struct pnp_dev *pnp)
>  	vga_switcheroo_unregister_handler();
>  	pci_dev_put(gmux_data->discrete_pdev);
>  	gmux_disable_interrupts(gmux_data);
> +	cancel_work_sync(&gmux_data->interrupt_work);
>  	if (gmux_data->gpe >= 0) {
>  		acpi_disable_gpe(NULL, gmux_data->gpe);
>  		acpi_remove_notify_handler(gmux_data->dhandle,

[Severity: High]
In gmux_probe(), INIT_WORK(&gmux_data->interrupt_work, gmux_notify_work) is
only called in the APPLE_GMUX_TYPE_MMIO case. On APPLE_GMUX_TYPE_PIO and
APPLE_GMUX_TYPE_INDEXED systems, won't calling
cancel_work_sync(&gmux_data->interrupt_work) unconditionally in gmux_remove()
pass an uninitialized work_struct (with work->func == NULL) to
__flush_work() and trigger a WARN_ON(!work->func) warning?

[Severity: Critical]
In gmux_remove(), is the teardown ordering around cancel_work_sync() safe
against a racing ACPI notification?

Currently gmux_remove() does:

	pci_dev_put(gmux_data->discrete_pdev);
	gmux_disable_interrupts(gmux_data);
	cancel_work_sync(&gmux_data->interrupt_work);
	if (gmux_data->gpe >= 0) {
		acpi_disable_gpe(NULL, gmux_data->gpe);
		acpi_remove_notify_handler(gmux_data->dhandle,
					   ACPI_DEVICE_NOTIFY,
					   &gmux_notify_handler);
	}
	...
	if (gmux_data->iomem_base) {
		iounmap(gmux_data->iomem_base);
		release_mem_region(gmux_data->iostart, gmux_data->iolen);
	} else
		release_region(gmux_data->iostart, gmux_data->iolen);
	apple_gmux_data = NULL;
	kfree(gmux_data);

First, pci_dev_put(gmux_data->discrete_pdev) and gmux_disable_interrupts() are
called before cancel_work_sync(), so an already-running gmux_notify_work() can
still dereference gmux_data->discrete_pdev in pm_runtime_resume() and call
gmux_enable_interrupts(gmux_data, 0) after interrupts were disabled.

Second, because acpi_disable_gpe() and acpi_remove_notify_handler() are called
after cancel_work_sync(&gmux_data->interrupt_work), any GPE or in-flight
execution of gmux_notify_handler() before acpi_remove_notify_handler()
completes will call schedule_work(&gmux_data->interrupt_work) again, causing
gmux_notify_work() to run concurrently with iounmap(gmux_data->iomem_base) and
kfree(gmux_data).

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261025040046.1602-1-atharvatiwarilinuxdev@gmail.com?part=4

  reply	other threads:[~2026-10-25  4:39 UTC|newest]

Thread overview: 36+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-25  3:59 [PATCH 00/13] Fixes for apple-gmux and runtime support Atharva Tiwari
2026-10-25  3:59 ` [PATCH 01/13] platform/apple-gmux: Fix power for MMIO systems Atharva Tiwari
2026-10-25  4:49   ` sashiko-bot
2026-10-25  3:59 ` [PATCH 02/13] PCI: Disable pcie port and access on D3 on specific amd devices Atharva Tiwari
2026-10-25  4:34   ` sashiko-bot
2026-10-27 23:00   ` Bjorn Helgaas
2026-10-28  0:08     ` Atharva Tiwari
2026-10-28  1:19       ` Atharva Tiwari
2026-10-25  3:59 ` [PATCH 03/13] platform/apple-gmux: Fix MMIO interface Atharva Tiwari
2026-10-25  4:27   ` sashiko-bot
2026-10-25 22:10   ` kernel test robot
2026-10-25  3:59 ` [PATCH 04/13] platform/apple-gmux: Fix Interrupts for MMIO systems Atharva Tiwari
2026-10-25  4:39   ` sashiko-bot [this message]
2026-10-25  3:59 ` [PATCH 05/13] platform/apple-gmux: Write switch state to EFI on MMIO devices Atharva Tiwari
2026-10-25  4:36   ` sashiko-bot
2026-10-25  3:59 ` [PATCH 06/13] platform/apple-gmux: Add parameter to configure boot gpu Atharva Tiwari
2026-10-25  4:33   ` sashiko-bot
2026-10-25  3:59 ` [PATCH 07/13] platform/apple-gmux: Add check to detect FSS Atharva Tiwari
2026-10-25  4:30   ` sashiko-bot
2026-10-25  3:59 ` [PATCH 08/13] platform/apple-gmux: Add documentation about iMacs Atharva Tiwari
2026-10-25  4:37   ` sashiko-bot
2026-10-25  3:59 ` [PATCH 09/13] drm/amdgpu: Add Apple GMUX runtime PM support Atharva Tiwari
2026-10-25  5:56   ` sashiko-bot
2026-10-25  3:59 ` [PATCH 10/13] drm/amdgpu: Catch register access while GMUX keeps the GPU off Atharva Tiwari
2026-10-25  4:40   ` sashiko-bot
2026-10-26 18:25   ` Alex Deucher
2026-10-27  8:29     ` Atharva Tiwari
2026-10-27 15:22       ` Alex Deucher
2026-10-25  3:59 ` [PATCH 11/13] drm/amd/display: Refuse DP AUX " Atharva Tiwari
2026-10-25  4:41   ` sashiko-bot
2026-10-26 18:25   ` Alex Deucher
2026-10-25  3:59 ` [PATCH 12/13] drm/amdgpu: Skip AC/DC notification while the GPU is runtime suspended Atharva Tiwari
2026-10-25  4:43   ` sashiko-bot
2026-10-26 18:18   ` Alex Deucher
2026-10-25  3:59 ` [PATCH 13/13] drm/amd/display: Skip panel power savings on eDP links without a panel Atharva Tiwari
2026-10-25  4:30   ` sashiko-bot

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=sashiko-outbox-184073@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=atharvatiwarilinuxdev@gmail.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=linux-pci@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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