[PATCH v1 07/30] ACPICA: Events: Clear GPE dispatch by owner during table unload
From: Pawel Chmielewski
Date: Tue Oct 20 2026 - 10:16:36 EST
From: ikaros <void0red@xxxxxxxxx>
When a table is unloaded, its GPE method handlers must be cleared to
prevent use-after-free. Add AcpiEvClearGpeDispatchByOwner() which walks
all GPE blocks and clears dispatch info for methods owned by the
unloading table. If a matching GPE is currently dispatching,
AE_ABORT_METHOD is returned and the unload is aborted.
Also add a dispatch-type re-check in AcpiEvAsynchExecuteGpeMethod to
handle the case where dispatch was cleared between scheduling and
execution.
Link: https://github.com/acpica/acpica/commit/13dcd9b19a46
Signed-off-by: ikaros <void0red@xxxxxxxxx>
Signed-off-by: Pawel Chmielewski <pawel.chmielewski@xxxxxxxxx>
---
drivers/acpi/acpica/acevents.h | 4 ++
drivers/acpi/acpica/evgpe.c | 97 ++++++++++++++++++++++++++++++++++
drivers/acpi/acpica/tbdata.c | 13 +++++
3 files changed, 114 insertions(+)
diff --git a/drivers/acpi/acpica/acevents.h b/drivers/acpi/acpica/acevents.h
index ccdc3725c773..4fe26c190c57 100644
--- a/drivers/acpi/acpica/acevents.h
+++ b/drivers/acpi/acpica/acevents.h
@@ -122,6 +122,10 @@ acpi_status acpi_ev_gpe_initialize(void);
ACPI_HW_DEPENDENT_RETURN_VOID(void
acpi_ev_update_gpes(acpi_owner_id table_owner_id))
+ACPI_HW_DEPENDENT_RETURN_STATUS(acpi_status
+ acpi_ev_clear_gpe_dispatch_by_owner(acpi_owner_id
+ owner_id))
+
acpi_status
acpi_ev_match_gpe_method(acpi_handle obj_handle,
u32 level, void *context, void **return_value);
diff --git a/drivers/acpi/acpica/evgpe.c b/drivers/acpi/acpica/evgpe.c
index 53b52efc7c2b..93e205c0709e 100644
--- a/drivers/acpi/acpica/evgpe.c
+++ b/drivers/acpi/acpica/evgpe.c
@@ -489,6 +489,18 @@ static void ACPI_SYSTEM_XFACE acpi_ev_asynch_execute_gpe_method(void *context)
case ACPI_GPE_DISPATCH_METHOD:
+ /*
+ * Re-check dispatch type. The table may have been unloaded
+ * and its GPE dispatch cleared while we were waiting.
+ */
+ if (ACPI_GPE_DISPATCH_TYPE(gpe_event_info->flags) !=
+ ACPI_GPE_DISPATCH_METHOD) {
+ ACPI_DEBUG_PRINT(
+ (ACPI_DB_EVENTS,
+ "GPE method dispatch cleared during wait\n"));
+ goto error_exit;
+ }
+
/* Allocate the evaluation information block */
info = ACPI_ALLOCATE_ZEROED(sizeof(struct acpi_evaluate_info));
@@ -535,6 +547,91 @@ static void ACPI_SYSTEM_XFACE acpi_ev_asynch_execute_gpe_method(void *context)
}
+/*******************************************************************************
+ *
+ * FUNCTION: acpi_ev_clear_gpe_dispatch_by_owner
+ *
+ * PARAMETERS: owner_id - Table owner_id being unloaded
+ *
+ * RETURN: Status
+ *
+ * DESCRIPTION: Clear GPE dispatch info for methods belonging to the table
+ * being unloaded. If any matching GPE is currently dispatching,
+ * it is not cleared and AE_ABORT_METHOD is returned.
+ *
+ ******************************************************************************/
+
+acpi_status acpi_ev_clear_gpe_dispatch_by_owner(acpi_owner_id owner_id)
+{
+ struct acpi_gpe_xrupt_info *gpe_xrupt_info;
+ struct acpi_gpe_block_info *gpe_block;
+ struct acpi_gpe_event_info *gpe_event_info;
+ u32 gpe_number;
+ acpi_cpu_flags flags;
+ acpi_status status = AE_OK;
+
+ ACPI_FUNCTION_TRACE_U32(ev_clear_gpe_dispatch_by_owner, owner_id);
+
+ if (owner_id == 0) {
+ return_ACPI_STATUS(AE_OK);
+ }
+
+ flags = acpi_os_acquire_lock(acpi_gbl_gpe_lock);
+
+ /* Walk all GPE interrupt blocks */
+
+ gpe_xrupt_info = acpi_gbl_gpe_xrupt_list_head;
+ while (gpe_xrupt_info) {
+ gpe_block = gpe_xrupt_info->gpe_block_list_head;
+ while (gpe_block) {
+ /* Walk all GPEs in this block */
+
+ for (gpe_number = 0; gpe_number < gpe_block->gpe_count;
+ gpe_number++) {
+ gpe_event_info =
+ &gpe_block->event_info[gpe_number];
+
+ if ((ACPI_GPE_DISPATCH_TYPE(
+ gpe_event_info->flags) ==
+ ACPI_GPE_DISPATCH_METHOD) &&
+ (gpe_event_info->dispatch.method_node) &&
+ (gpe_event_info->dispatch.method_node
+ ->owner_id == owner_id)) {
+ /* GPE method belongs to table being unloaded */
+
+ if (gpe_event_info
+ ->disable_for_dispatch) {
+ /* Handler is executing, cannot clear */
+ status = AE_ABORT_METHOD;
+ } else {
+ /* Clear only the dispatch type, preserve other flags */
+ gpe_event_info->flags &=
+ ~ACPI_GPE_DISPATCH_MASK;
+ gpe_event_info->dispatch
+ .method_node = NULL;
+
+ /*
+ * Hardware-disable the GPE so a late interrupt
+ * cannot fire and land in the "no handler" default
+ * branch with disable_for_dispatch stuck at TRUE.
+ */
+ (void)acpi_hw_low_set_gpe(
+ gpe_event_info,
+ ACPI_GPE_DISABLE);
+ }
+ }
+ }
+
+ gpe_block = gpe_block->next;
+ }
+
+ gpe_xrupt_info = gpe_xrupt_info->next;
+ }
+
+ acpi_os_release_lock(acpi_gbl_gpe_lock, flags);
+ return_ACPI_STATUS(status);
+}
+
/*******************************************************************************
*
* FUNCTION: acpi_ev_asynch_enable_gpe
diff --git a/drivers/acpi/acpica/tbdata.c b/drivers/acpi/acpica/tbdata.c
index 07af035aa4de..144f15011eff 100644
--- a/drivers/acpi/acpica/tbdata.c
+++ b/drivers/acpi/acpica/tbdata.c
@@ -1059,6 +1059,19 @@ acpi_status acpi_tb_unload_table(u32 table_index)
acpi_tb_notify_table(ACPI_TABLE_EVENT_UNLOAD, table);
}
+#if (!ACPI_REDUCED_HARDWARE)
+ /*
+ * Clear GPE dispatch for methods owned by the table being unloaded.
+ * If any matching GPE is currently dispatching, AE_ABORT_METHOD is
+ * returned and the unload cannot proceed.
+ */
+ status = acpi_ev_clear_gpe_dispatch_by_owner(
+ acpi_gbl_root_table_list.tables[table_index].owner_id);
+ if (ACPI_FAILURE(status)) {
+ return_ACPI_STATUS(status);
+ }
+#endif /* !ACPI_REDUCED_HARDWARE */
+
/* Delete the portion of the namespace owned by this table */
status = acpi_tb_delete_namespace_by_owner(table_index);
--
2.50.1