Re: [PATCH v3] i2c: i801: Safely share SMBus with BIOS/ACPI

From: Wolfram Sang
Date: Mon Nov 29 2021 - 04:15:36 EST


On Sat, Jun 26, 2021 at 02:41:13PM +0900, Hector Martin wrote:
> The i801 controller provides a locking mechanism that the OS is supposed
> to use to safely share the SMBus with ACPI AML or other firmware.
>
> Previously, Linux attempted to get out of the way of ACPI AML entirely,
> but left the bus locked if it used it before the first AML access. This
> causes AML implementations that *do* attempt to safely share the bus
> to time out if Linux uses it first; notably, this regressed ACPI video
> backlight controls on 2015 iMacs after 01590f361e started instantiating
> SPD EEPROMs on boot.
>
> Commit 065b6211a8 fixed the immediate problem of leaving the bus locked,
> but we can do better. The controller does have a proper locking mechanism,
> so let's use it as intended. Since we can't rely on the BIOS doing this
> properly, we implement the following logic:
>
> - If ACPI AML uses the bus at all, we make a note and disable power
> management. The latter matches already existing behavior.
> - When we want to use the bus, we attempt to lock it first. If the
> locking attempt times out, *and* ACPI hasn't tried to use the bus at
> all yet, we cautiously go ahead and assume the BIOS forgot to unlock
> the bus after boot. This preserves existing behavior.
> - We always unlock the bus after a transfer.
> - If ACPI AML tries to use the bus (except trying to lock it) while
> we're in the middle of a transfer, or after we've determined
> locking is broken, we know we cannot safely share the bus and give up.
>
> Upon first usage of SMBus by ACPI AML, if nothing has gone horribly
> wrong so far, users will see:
>
> i801_smbus 0000:00:1f.4: SMBus controller is shared with ACPI AML. This seems safe so far.
>
> If locking the SMBus times out, users will see:
>
> i801_smbus 0000:00:1f.4: BIOS left SMBus locked
>
> And if ACPI AML tries to use the bus concurrently with Linux, or it
> previously used the bus and we failed to subsequently lock it as
> above, the driver will give up and users will get:
>
> i801_smbus 0000:00:1f.4: BIOS uses SMBus unsafely
> i801_smbus 0000:00:1f.4: Driver SMBus register access inhibited
>
> This fixes the regression introduced by 01590f361e, and further allows
> safely sharing the SMBus on 2015 iMacs. Tested by running `i2cdump` in a
> loop while changing backlight levels via the ACPI video device.
>
> Fixes: 01590f361e ("i2c: i801: Instantiate SPD EEPROMs automatically")
> Cc: <stable@xxxxxxxxxxxxxxx>
> Signed-off-by: Hector Martin <marcan@xxxxxxxxx>

Jean, Heiner, what do we do with this topic?

> ---
> drivers/i2c/busses/i2c-i801.c | 96 ++++++++++++++++++++++++++++-------
> 1 file changed, 79 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c
> index 04a1e38f2a6f..03be6310d6d7 100644
> --- a/drivers/i2c/busses/i2c-i801.c
> +++ b/drivers/i2c/busses/i2c-i801.c
> @@ -287,11 +287,18 @@ struct i801_priv {
> #endif
> struct platform_device *tco_pdev;
>
> + /* BIOS left the controller marked busy. */
> + bool inuse_stuck;
> /*
> - * If set to true the host controller registers are reserved for
> - * ACPI AML use. Protected by acpi_lock.
> + * If set to true, ACPI AML uses the host controller registers.
> + * Protected by acpi_lock.
> */
> - bool acpi_reserved;
> + bool acpi_usage;
> + /*
> + * If set to true, ACPI AML uses the host controller registers in an
> + * unsafe way. Protected by acpi_lock.
> + */
> + bool acpi_unsafe;
> struct mutex acpi_lock;
> };
>
> @@ -854,10 +861,37 @@ static s32 i801_access(struct i2c_adapter *adap, u16 addr,
> int hwpec;
> int block = 0;
> int ret = 0, xact = 0;
> + int timeout = 0;
> struct i801_priv *priv = i2c_get_adapdata(adap);
>
> + /*
> + * The controller provides a bit that implements a mutex mechanism
> + * between users of the bus. First, try to lock the hardware mutex.
> + * If this doesn't work, we give up trying to do this, but then
> + * bail if ACPI uses SMBus at all.
> + */
> + if (!priv->inuse_stuck) {
> + while (inb_p(SMBHSTSTS(priv)) & SMBHSTSTS_INUSE_STS) {
> + if (++timeout >= MAX_RETRIES) {
> + dev_warn(&priv->pci_dev->dev,
> + "BIOS left SMBus locked\n");
> + priv->inuse_stuck = true;
> + break;
> + }
> + usleep_range(250, 500);
> + }
> + }
> +
> mutex_lock(&priv->acpi_lock);
> - if (priv->acpi_reserved) {
> + if (priv->acpi_usage && priv->inuse_stuck && !priv->acpi_unsafe) {
> + priv->acpi_unsafe = true;
> +
> + dev_warn(&priv->pci_dev->dev, "BIOS uses SMBus unsafely\n");
> + dev_warn(&priv->pci_dev->dev,
> + "Driver SMBus register access inhibited\n");
> + }
> +
> + if (priv->acpi_unsafe) {
> mutex_unlock(&priv->acpi_lock);
> return -EBUSY;
> }
> @@ -1639,6 +1673,16 @@ static bool i801_acpi_is_smbus_ioport(const struct i801_priv *priv,
> address <= pci_resource_end(priv->pci_dev, SMBBAR);
> }
>
> +static acpi_status
> +i801_acpi_do_access(u32 function, acpi_physical_address address,
> + u32 bits, u64 *value)
> +{
> + if ((function & ACPI_IO_MASK) == ACPI_READ)
> + return acpi_os_read_port(address, (u32 *)value, bits);
> + else
> + return acpi_os_write_port(address, (u32)*value, bits);
> +}
> +
> static acpi_status
> i801_acpi_io_handler(u32 function, acpi_physical_address address, u32 bits,
> u64 *value, void *handler_context, void *region_context)
> @@ -1648,17 +1692,38 @@ i801_acpi_io_handler(u32 function, acpi_physical_address address, u32 bits,
> acpi_status status;
>
> /*
> - * Once BIOS AML code touches the OpRegion we warn and inhibit any
> - * further access from the driver itself. This device is now owned
> - * by the system firmware.
> + * Non-i801 accesses pass through.
> */
> - mutex_lock(&priv->acpi_lock);
> + if (!i801_acpi_is_smbus_ioport(priv, address))
> + return i801_acpi_do_access(function, address, bits, value);
>
> - if (!priv->acpi_reserved && i801_acpi_is_smbus_ioport(priv, address)) {
> - priv->acpi_reserved = true;
> + if (!mutex_trylock(&priv->acpi_lock)) {
> + mutex_lock(&priv->acpi_lock);
> + /*
> + * This better be a read of the status register to acquire
> + * the lock...
> + */
> + if (!priv->acpi_unsafe &&
> + !(address == SMBHSTSTS(priv) &&
> + (function & ACPI_IO_MASK) == ACPI_READ)) {
> + /*
> + * Uh-oh, ACPI AML is trying to do something with the
> + * controller without locking it properly.
> + */
> + priv->acpi_unsafe = true;
> +
> + dev_warn(&pdev->dev, "BIOS uses SMBus unsafely\n");
> + dev_warn(&pdev->dev,
> + "Driver SMBus register access inhibited\n");
> + }
> + }
>
> - dev_warn(&pdev->dev, "BIOS is accessing SMBus registers\n");
> - dev_warn(&pdev->dev, "Driver SMBus register access inhibited\n");
> + if (!priv->acpi_usage) {
> + priv->acpi_usage = true;
> +
> + if (!priv->acpi_unsafe)
> + dev_info(&pdev->dev,
> + "SMBus controller is shared with ACPI AML. This seems safe so far.\n");
>
> /*
> * BIOS is accessing the host controller so prevent it from
> @@ -1667,10 +1732,7 @@ i801_acpi_io_handler(u32 function, acpi_physical_address address, u32 bits,
> pm_runtime_get_sync(&pdev->dev);
> }
>
> - if ((function & ACPI_IO_MASK) == ACPI_READ)
> - status = acpi_os_read_port(address, (u32 *)value, bits);
> - else
> - status = acpi_os_write_port(address, (u32)*value, bits);
> + status = i801_acpi_do_access(function, address, bits, value);
>
> mutex_unlock(&priv->acpi_lock);
>
> @@ -1706,7 +1768,7 @@ static void i801_acpi_remove(struct i801_priv *priv)
> ACPI_ADR_SPACE_SYSTEM_IO, i801_acpi_io_handler);
>
> mutex_lock(&priv->acpi_lock);
> - if (priv->acpi_reserved)
> + if (priv->acpi_usage)
> pm_runtime_put(&priv->pci_dev->dev);
> mutex_unlock(&priv->acpi_lock);
> }
> --
> 2.32.0
>

Attachment: signature.asc
Description: PGP signature