i2c: i801: Fix interrupt storm from SMB_ALERT signal
authorJarkko Nikula <jarkko.nikula@linux.intel.com>
Wed, 17 Nov 2021 09:45:09 +0000 (11:45 +0200)
committerWolfram Sang <wsa@kernel.org>
Tue, 23 Nov 2021 09:43:50 +0000 (10:43 +0100)
Currently interrupt storm will occur from i2c-i801 after first
transaction if SMB_ALERT signal is enabled and ever asserted. It is
enough if the signal is asserted once even before the driver is loaded
and does not recover because that interrupt is not acknowledged.

This fix aims to fix it by two ways:
- Add acknowledging for the SMB_ALERT interrupt status
- Disable the SMB_ALERT interrupt on platforms where possible since the
  driver currently does not make use for it

Acknowledging resets the SMB_ALERT interrupt status on all platforms and
also should help to avoid interrupt storm on older platforms where the
SMB_ALERT interrupt disabling is not available.

For simplicity this fix reuses the host notify feature for disabling and
restoring original register value.

Link: https://bugzilla.kernel.org/show_bug.cgi?id=177311
Reported-by: ck+kernelbugzilla@bl4ckb0x.de
Reported-by: stephane.poignant@protonmail.com
Signed-off-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Reviewed-by: Jean Delvare <jdelvare@suse.de>
Tested-by: Jean Delvare <jdelvare@suse.de>
Signed-off-by: Wolfram Sang <wsa@kernel.org>
drivers/i2c/busses/i2c-i801.c

index ed271274250b1915aed99f471bda0396c68c02c2..41446f9cc52da96db1084ad06a94c08a91d93374 100644 (file)
 #define SMBSLVSTS_HST_NTFY_STS BIT(0)
 
 /* Host Notify Command register bits */
+#define SMBSLVCMD_SMBALERT_DISABLE     BIT(2)
 #define SMBSLVCMD_HST_NTFY_INTREN      BIT(0)
 
 #define STATUS_ERROR_FLAGS     (SMBHSTSTS_FAILED | SMBHSTSTS_BUS_ERR | \
@@ -642,12 +643,20 @@ static irqreturn_t i801_isr(int irq, void *dev_id)
                i801_isr_byte_done(priv);
 
        /*
-        * Clear irq sources and report transaction result.
+        * Clear remaining IRQ sources: Completion of last command, errors
+        * and the SMB_ALERT signal. SMB_ALERT status is set after signal
+        * assertion independently of the interrupt generation being blocked
+        * or not so clear it always when the status is set.
+        */
+       status &= SMBHSTSTS_INTR | STATUS_ERROR_FLAGS | SMBHSTSTS_SMBALERT_STS;
+       if (status)
+               outb_p(status, SMBHSTSTS(priv));
+       status &= ~SMBHSTSTS_SMBALERT_STS; /* SMB_ALERT not reported */
+       /*
+        * Report transaction result.
         * ->status must be cleared before the next transaction is started.
         */
-       status &= SMBHSTSTS_INTR | STATUS_ERROR_FLAGS;
        if (status) {
-               outb_p(status, SMBHSTSTS(priv));
                priv->status = status;
                complete(&priv->done);
        }
@@ -975,9 +984,13 @@ static void i801_enable_host_notify(struct i2c_adapter *adapter)
        if (!(priv->features & FEATURE_HOST_NOTIFY))
                return;
 
-       if (!(SMBSLVCMD_HST_NTFY_INTREN & priv->original_slvcmd))
-               outb_p(SMBSLVCMD_HST_NTFY_INTREN | priv->original_slvcmd,
-                      SMBSLVCMD(priv));
+       /*
+        * Enable host notify interrupt and block the generation of interrupt
+        * from the SMB_ALERT signal because the driver does not support
+        * SMBus Alert.
+        */
+       outb_p(SMBSLVCMD_HST_NTFY_INTREN | SMBSLVCMD_SMBALERT_DISABLE |
+              priv->original_slvcmd, SMBSLVCMD(priv));
 
        /* clear Host Notify bit to allow a new notification */
        outb_p(SMBSLVSTS_HST_NTFY_STS, SMBSLVSTS(priv));