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
next prev parent 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