Skip to contentSkip to conversation

ACPI / ACPICA: Fix problems related with GPE reference counting

PATCH New Rafael J. Wysocki 1 participant 1 message May 20, 2010

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 ACPI / ACPICA: Fix problems related with GPE reference counting

Hi,

The patch below needs some more testing, but I _think_ it should work. It's against 2.6.34 with some ACPICA changes that shouldn't interfere with it. Tomorrow I'll rebase it on top of the current Linus' tree.

The problems are described in the changelog, as well as the changes to address them.

Please tell me what you think.

Rafael

---
From: Rafael J. Wysocki <[email protected]>

There are a few problems with the recently introduced GPE reference counting mechanism.

First of all, acpi_set_gpe() won't enable wakeup-only GPEs, because it uses acpi_ev_enable_gpe() that, in turn, calls acpi_hw_write_gpe_enable_reg() that will only enable the GPE if its bit is set in the register's enable_for_run. Worse yet, acpi_hw_write_gpe_enable_reg() always writes the entire enable_for_run mask into the register, so in theory it may enable more GPEs than just the one we want to enable (for example, it may enable GPEs in the same register that were disabled by acpi_hw_low_disable_gpe() previously).

Second, the enable_for_run and enable_for_wake masks of the GPE registers only change when the GPEs' runtime_count and wakeup_count fields change from zero to nonzero and the other way around, so it doesn't make sense to update the enable_for_run and enable_for_wake masks outside of acpi_enable_gpe() and acpi_disable_gpe().

To address these problems, call acpi_ev_update_gpe_enable_masks() directly from acpi_enable_gpe() and acpi_disable_gpe() where the GPE's runtime_count and wakeup_count counters change in such a way that its register's enable_for_run and enable_for_wake masks have to be updated. Change acpi_hw_low_disable_gpe() into acpi_hw_low_set_gpe() that can set as well as clear the appropriate bit in the GPE's register and use it wherever necessary. Modify acpi_hw_write_gpe_enable_reg() so that it only sets the GPE bit corresponding to its argument in the register and only if that bit is set in the register's enable_for_run mask. Finally, remove acpi_ev_enable_gpe() and acpi_ev_disable_gpe() that aren't necessary any more.

In addition to that, to reduce code duplication, move the computation of a GPE's register bit mask to a separate function and call it wherever that computation has to be carried out.

---
 drivers/acpi/acpica/achware.h  |    6 ++
 drivers/acpi/acpica/evgpe.c    |   86 ++---------------------------------
 drivers/acpi/acpica/evgpeblk.c |   13 ++++-
 drivers/acpi/acpica/evxfevnt.c |   27 ++++++++---
 drivers/acpi/acpica/hwgpe.c    |  100 ++++++++++++++++++++++++++++++++---------
 5 files changed, 121 insertions(+), 111 deletions(-)
drivers/acpi/acpica/hwgpe.c +79 -21
Index: linux-2.6/drivers/acpi/acpica/hwgpe.c
===================================================================
@@ -57,21 +57,45 @@ acpi_hw_enable_wakeup_gpe_block(struct a
 
 /******************************************************************************
  *
- * FUNCTION:	acpi_hw_low_disable_gpe
+ * FUNCTION:	acpi_hw_gpe_register_bit
+ *
+ * PARAMETERS:	gpe_event_info	    - Info block for the GPE
+ *		gpe_register_info   - Info block for the GPE register
+ *
+ * RETURN:	Status
+ *
+ * DESCRIPTION:	Compute the enable mask with one bit corresponding to the given
+ *		GPE set.
+ *
+ ******************************************************************************/
+
+u32 acpi_hw_gpe_register_bit(struct acpi_gpe_event_info *gpe_event_info,
+			     struct acpi_gpe_register_info *gpe_register_info)
+{
+	return (u32)1 << (gpe_event_info->gpe_number -
+				gpe_register_info->base_gpe_number);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION:	acpi_hw_low_set_gpe
  *
  * PARAMETERS:	gpe_event_info	    - Info block for the GPE to be disabled
+ *		action		    - Enable or disable
  *
  * RETURN:	Status
  *
- * DESCRIPTION: Disable a single GPE in the enable register.
+ * DESCRIPTION: Enable or disable a single GPE in its enable register.
  *
  ******************************************************************************/
 
-acpi_status acpi_hw_low_disable_gpe(struct acpi_gpe_event_info *gpe_event_info)
+acpi_status acpi_hw_low_set_gpe(struct acpi_gpe_event_info *gpe_event_info,
+				u8 action)
 {
 	struct acpi_gpe_register_info *gpe_register_info;
 	acpi_status status;
 	u32 enable_mask;
+	u32 register_bit;
 
 	/* Get the info block for the entire GPE register */
 
@@ -80,6 +104,9 @@ acpi_status acpi_hw_low_disable_gpe(stru
 		return (AE_NOT_EXIST);
 	}
 
+	register_bit = acpi_hw_gpe_register_bit(gpe_event_info,
+						gpe_register_info);
+
 	/* Get current value of the enable register that contains this GPE */
 
 	status = acpi_hw_read(&enable_mask, &gpe_register_info->enable_address);
@@ -87,11 +114,22 @@ acpi_status acpi_hw_low_disable_gpe(stru
 		return (status);
 	}
 
-	/* Clear just the bit that corresponds to this GPE */
+	/* Set ot clear just the bit that corresponds to this GPE */
+
+	switch (action) {
+	case ACPI_GPE_ENABLE:
+		ACPI_SET_BIT(enable_mask, register_bit);
+		break;
+
+	case ACPI_GPE_DISABLE:
+		ACPI_CLEAR_BIT(enable_mask, register_bit);
+		break;
+
+	default:
+		ACPI_ERROR((AE_INFO, "Invalid action\n"));
+		return (AE_BAD_PARAMETER);
+	}
 
-	ACPI_CLEAR_BIT(enable_mask, ((u32)1 <<
-				     (gpe_event_info->gpe_number -
-				      gpe_register_info->base_gpe_number)));
 
 	/* Write the updated enable mask */
 
@@ -118,6 +156,8 @@ acpi_hw_write_gpe_enable_reg(struct acpi
 {
 	struct acpi_gpe_register_info *gpe_register_info;
 	acpi_status status;
+	u32 enable_mask;
+	u32 register_bit;
 
 	ACPI_FUNCTION_ENTRY();
 
@@ -128,10 +168,21 @@ acpi_hw_write_gpe_enable_reg(struct acpi
 		return (AE_NOT_EXIST);
 	}
 
-	/* Write the entire GPE (runtime) enable register */
+	register_bit = acpi_hw_gpe_register_bit(gpe_event_info,
+						gpe_register_info);
 
-	status = acpi_hw_write(gpe_register_info->enable_for_run,
-			       &gpe_register_info->enable_address);
+	/* Get current value of the enable register that contains this GPE */
+
+	status = acpi_hw_read(&enable_mask, &gpe_register_info->enable_address);
+	if (ACPI_FAILURE(status)) {
+		return (status);
+	}
+
+	if (register_bit & gpe_register_info->enable_for_run) {
+		ACPI_SET_BIT(enable_mask, register_bit);
+		status = acpi_hw_write(enable_mask,
+					&gpe_register_info->enable_address);
+	}
 
 	return (status);
 }
@@ -150,21 +201,28 @@ acpi_hw_write_gpe_enable_reg(struct acpi
 
 acpi_status acpi_hw_clear_gpe(struct acpi_gpe_event_info * gpe_event_info)
 {
+	struct acpi_gpe_register_info *gpe_register_info;
 	acpi_status status;
-	u8 register_bit;
+	u32 register_bit;
 
 	ACPI_FUNCTION_ENTRY();
 
-	register_bit = (u8)(1 <<
-			    (gpe_event_info->gpe_number -
-			     gpe_event_info->register_info->base_gpe_number));
+	/* Get the info block for the entire GPE register */
+
+	gpe_register_info = gpe_event_info->register_info;
+	if (!gpe_register_info) {
+		return (AE_NOT_EXIST);
+	}
+
+	register_bit = acpi_hw_gpe_register_bit(gpe_event_info,
+						gpe_register_info);
 
 	/*
 	 * Write a one to the appropriate bit in the status register to
 	 * clear this GPE.
 	 */
 	status = acpi_hw_write(register_bit,
-			       &gpe_event_info->register_info->status_address);
+				&gpe_register_info->status_address);
 
 	return (status);
 }
@@ -187,7 +245,7 @@ acpi_hw_get_gpe_status(struct acpi_gpe_e
 		       acpi_event_status * event_status)
 {
 	u32 in_byte;
-	u8 register_bit;
+	u32 register_bit;
 	struct acpi_gpe_register_info *gpe_register_info;
 	acpi_status status;
 	acpi_event_status local_event_status = 0;
@@ -201,12 +259,12 @@ acpi_hw_get_gpe_status(struct acpi_gpe_e
 	/* Get the info block for the entire GPE register */
 
 	gpe_register_info = gpe_event_info->register_info;
+	if (!gpe_register_info) {
+		return (AE_NOT_EXIST);
+	}
 
-	/* Get the register bitmask for this GPE */
-
-	register_bit = (u8)(1 <<
-			    (gpe_event_info->gpe_number -
-			     gpe_event_info->register_info->base_gpe_number));
+	register_bit = acpi_hw_gpe_register_bit(gpe_event_info,
+						gpe_register_info);
 
 	/* GPE currently enabled? (enabled for runtime?) */
 
drivers/acpi/acpica/achware.h +5 -1
Index: linux-2.6/drivers/acpi/acpica/achware.h
===================================================================
@@ -90,7 +90,11 @@ acpi_status acpi_hw_write_port(acpi_io_a
 /*
  * hwgpe - GPE support
  */
-acpi_status acpi_hw_low_disable_gpe(struct acpi_gpe_event_info *gpe_event_info);
+u32 acpi_hw_gpe_register_bit(struct acpi_gpe_event_info *gpe_event_info,
+			     struct acpi_gpe_register_info *gpe_register_info);
+
+acpi_status acpi_hw_low_set_gpe(struct acpi_gpe_event_info *gpe_event_info,
+				u8 action);
 
 acpi_status
 acpi_hw_write_gpe_enable_reg(struct acpi_gpe_event_info *gpe_event_info);
drivers/acpi/acpica/evgpe.c +5 -81

122 lines not on this page · raw message

drivers/acpi/acpica/evxfevnt.c +20 -7
Index: linux-2.6/drivers/acpi/acpica/evxfevnt.c
===================================================================
@@ -235,11 +235,17 @@ acpi_status acpi_set_gpe(acpi_handle gpe
 
 	switch (action) {
 	case ACPI_GPE_ENABLE:
-		status = acpi_ev_enable_gpe(gpe_event_info);
+		/* Clear the GPE (of stale events), then enable it */
+		status = acpi_hw_clear_gpe(gpe_event_info);
+		if (ACPI_FAILURE(status))
+			break;
+
+		/* Enable the requested GPE */
+		status = acpi_hw_low_set_gpe(gpe_event_info, ACPI_GPE_ENABLE);
 		break;
 
 	case ACPI_GPE_DISABLE:
-		status = acpi_ev_disable_gpe(gpe_event_info);
+		status = acpi_hw_low_set_gpe(gpe_event_info, ACPI_GPE_DISABLE);
 		break;
 
 	default:
@@ -291,7 +297,10 @@ acpi_status acpi_enable_gpe(acpi_handle 
 
 	if (type & ACPI_GPE_TYPE_RUNTIME) {
 		if (++gpe_event_info->runtime_count == 1) {
-			status = acpi_ev_enable_gpe(gpe_event_info);
+			status = acpi_ev_update_gpe_enable_masks(gpe_event_info);
+			if (ACPI_SUCCESS(status))
+				status = acpi_hw_low_set_gpe(gpe_event_info,
+							     ACPI_GPE_ENABLE);
 			if (ACPI_FAILURE(status))
 				gpe_event_info->runtime_count--;
 		}
@@ -308,7 +317,7 @@ acpi_status acpi_enable_gpe(acpi_handle 
 		 * system into a sleep state.
 		 */
 		if (++gpe_event_info->wakeup_count == 1)
-			acpi_ev_update_gpe_enable_masks(gpe_event_info);
+			status = acpi_ev_update_gpe_enable_masks(gpe_event_info);
 	}
 
 unlock_and_exit:
@@ -351,8 +360,12 @@ acpi_status acpi_disable_gpe(acpi_handle
 	}
 
 	if ((type & ACPI_GPE_TYPE_RUNTIME) && gpe_event_info->runtime_count) {
-		if (--gpe_event_info->runtime_count == 0)
-			status = acpi_ev_disable_gpe(gpe_event_info);
+		if (--gpe_event_info->runtime_count == 0) {
+			status = acpi_ev_update_gpe_enable_masks(gpe_event_info);
+			if (ACPI_SUCCESS(status))
+				status = acpi_hw_low_set_gpe(gpe_event_info,
+							     ACPI_GPE_DISABLE);
+		}
 	}
 
 	if ((type & ACPI_GPE_TYPE_WAKE) && gpe_event_info->wakeup_count) {
@@ -361,7 +374,7 @@ acpi_status acpi_disable_gpe(acpi_handle
 		 * states, so we don't need to disable them here.
 		 */
 		if (--gpe_event_info->wakeup_count == 0)
-			acpi_ev_update_gpe_enable_masks(gpe_event_info);
+			status = acpi_ev_update_gpe_enable_masks(gpe_event_info);
 	}
 
 unlock_and_exit:
drivers/acpi/acpica/evgpeblk.c +12 -1
Index: linux-2.6/drivers/acpi/acpica/evgpeblk.c
===================================================================
@@ -1016,6 +1016,18 @@ acpi_ev_initialize_gpe_block(struct acpi
 			/* Get the info block for this particular GPE */
 			gpe_index = (acpi_size)i * ACPI_GPE_REGISTER_WIDTH + j;
 			gpe_event_info = &gpe_block->event_info[gpe_index];
+			gpe_number = gpe_index + gpe_block->block_base_number;
+
+			/*
+			 * Ensure that the register bit corresponding to this
+			 * GPE are clear in its register's enable masks.
+			 */
+			status = acpi_ev_update_gpe_enable_masks(gpe_event_info);
+			if (ACPI_FAILURE(status)) {
+				ACPI_ERROR((AE_INFO, "Failed to update enable "
+					"masks for GPE %02X\n", gpe_number));
+				continue;
+			}
 
 			if (gpe_event_info->flags & ACPI_GPE_CAN_WAKE) {
 				wake_gpe_count++;
@@ -1026,7 +1038,6 @@ acpi_ev_initialize_gpe_block(struct acpi
 			if (!(gpe_event_info->flags & ACPI_GPE_DISPATCH_METHOD))
 				continue;
 
-			gpe_number = gpe_index + gpe_block->block_base_number;
 			status = acpi_enable_gpe(gpe_device, gpe_number,
 						ACPI_GPE_TYPE_RUNTIME);
 			if (ACPI_FAILURE(status))