Subject: Add some delay in EC GPE handler to avoid EC GPE interrupt storm From: Zhao Yakui According to ACPI spec the EC controller uses the pulse interrupt model to trigger EC GPE interrupt. After EC GPE interrupt is received, OS will check the EC status and serve the EC GPE handler. But if the EC interrupt pulse is too wide on some broken BIOS, several ACPI interrupts will be triggered although only one EC interrupt pulse is generated. Maybe on some laptops the issue will be more serious. In such case OS will read the EC status I/O port to check the EC status in very high frequency,which causes that keystrokes are lost and the system can't work well. Although the issue can be fixed by disabling EC GPE, it will cause that OS can't receive the EC notification event and the system can't work well. For example: there is no ACPI LID/AC/Battery/Thermal event notification. Maybe it will be appropriate that some delay is added in the EC GPE handler on some broken BIOS. The delay won't be added for most laptops.Only when more than five interrupts happen in the same jiffies and EC status are the same, OS will add some delay in the EC GPE handler. If the same issue still happens after adding delay,the delay time will be increased.But the max delay time is ten microseconds. After the patch is applied on the laptop of bug 9998, the EC GPE interrupts are not increased as fast as before and the system can work well. http://bugzilla.kernel.org/show_bug.cgi?id=9998 Signed-off-by: Zhao Yakui --- drivers/acpi/ec.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) Index: linux-2.6/drivers/acpi/ec.c =================================================================== --- linux-2.6.orig/drivers/acpi/ec.c +++ linux-2.6/drivers/acpi/ec.c @@ -55,6 +55,8 @@ #define ACPI_EC_FLAG_IBF 0x02 /* Input buffer full */ #define ACPI_EC_FLAG_BURST 0x10 /* burst mode */ #define ACPI_EC_FLAG_SCI 0x20 /* EC-SCI occurred */ +#define EC_IRQ_NUM 5 /* the EC interrupt storm threshold */ +#define EC_MAX_UDELAY 10 /* the max udelay time */ /* EC commands */ enum ec_command { @@ -102,12 +104,22 @@ static struct acpi_ec { unsigned long data_addr; unsigned long global_lock; unsigned long flags; + unsigned long pre_jiffies; + /* record the jiffies when last EC interrupt happens */ struct mutex lock; wait_queue_head_t wait; struct list_head list; struct delayed_work work; atomic_t irq_count; u8 handlers_installed; + u8 pre_state; + /* record the EC status when last EC interrrupt happens */ + atomic_t ec_irq_count; + unsigned long udelay; + /* + * this is for the input parameter of udelay in EC GPE interrupt. + * the max value is IRQ_MAX_UDELAY.(10) + */ } *boot_ec, *first_ec; /* @@ -505,14 +517,65 @@ static void acpi_ec_gpe_query(void *ec_c mutex_unlock(&ec->lock); } + /******************************************************************************* + * + * FUNCTION: acpi_ec_gpe_delay + * + * PARAMETERS: struct acpi_ec *ec, the EC device + u8 state - the EC status when EC GPE interrupt happens + * + * RETURN: No + * + * DESCRIPTION: detect whether there exists EC GPE interrupt storm. If yes, it + will try to add some delay to reduce the number of EC GPE + interrupts. +******************************************************************************/ +static void acpi_ec_gpe_delay(struct acpi_ec *ec, u8 state) +{ + static int warn_done; + if ((jiffies == ec->pre_jiffies) && (state == ec->pre_state)) { + atomic_inc(&ec->ec_irq_count); + if (atomic_read(&ec->ec_irq_count) > EC_IRQ_NUM) { + /* + * If the ec_irq_count is above EC_IRQ_NUM, it will + * be regarded as EC GPE interrupt storm. We will + * try to add some udelay in acpi_ec_gpe_delay. + */ + atomic_set(&ec->ec_irq_count, 0); + if (ec->udelay > EC_MAX_UDELAY) { + if (!warn_done) { + printk(KERN_WARNING "EC GPE interrupt" + " storm. And it is hardware issue\n"); + warn_done++; + } + } else { + /* + * the input parameter of udelay will be + * increased. + */ + ec->udelay = ec->udelay + 1; + } + } + } else { + ec->pre_jiffies = jiffies; + ec->pre_state = state; + atomic_set(&ec->ec_irq_count, 0); + } + if (ec->udelay) + udelay(ec->udelay); +} + static u32 acpi_ec_gpe_handler(void *data) { acpi_status status = AE_OK; struct acpi_ec *ec = data; - u8 state = acpi_ec_read_status(ec); + u8 state; pr_debug(PREFIX "~~~> interrupt\n"); + state = acpi_ec_read_status(ec); + + acpi_ec_gpe_delay(ec, state); if (state & ACPI_EC_FLAG_SCI) { if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) status = acpi_os_execute(OSL_EC_BURST_HANDLER, @@ -672,6 +735,8 @@ static struct acpi_ec *make_acpi_ec(void INIT_LIST_HEAD(&ec->list); INIT_DELAYED_WORK_DEFERRABLE(&ec->work, do_ec_poll); atomic_set(&ec->irq_count, 0); + ec->pre_jiffies = jiffies; + atomic_set(&ec->ec_irq_count, 0); return ec; }