| From a7ae81952cdab56a1277bd2f9ed7284c0f575120 Mon Sep 17 00:00:00 2001 |
| From: Mika Westerberg <mika.westerberg@linux.intel.com> |
| Date: Thu, 9 Jun 2016 16:56:28 +0300 |
| Subject: i2c: i801: Allow ACPI SystemIO OpRegion to conflict with PCI BAR |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| From: Mika Westerberg <mika.westerberg@linux.intel.com> |
| |
| commit a7ae81952cdab56a1277bd2f9ed7284c0f575120 upstream. |
| |
| Many Intel systems the BIOS declares a SystemIO OpRegion below the SMBus |
| PCI device as can be seen in ACPI DSDT table from Lenovo Yoga 900: |
| |
| Device (SBUS) |
| { |
| OperationRegion (SMBI, SystemIO, (SBAR << 0x05), 0x10) |
| Field (SMBI, ByteAcc, NoLock, Preserve) |
| { |
| HSTS, 8, |
| Offset (0x02), |
| HCON, 8, |
| HCOM, 8, |
| TXSA, 8, |
| DAT0, 8, |
| DAT1, 8, |
| HBDR, 8, |
| PECR, 8, |
| RXSA, 8, |
| SDAT, 16 |
| } |
| |
| There are also bunch of AML methods that that the BIOS can use to access |
| these fields. Most of the systems in question AML methods accessing the |
| SMBI OpRegion are never used. |
| |
| Now, because of this SMBI OpRegion many systems fail to load the SMBus |
| driver with an error looking like one below: |
| |
| ACPI Warning: SystemIO range 0x0000000000003040-0x000000000000305F |
| conflicts with OpRegion 0x0000000000003040-0x000000000000304F |
| (\_SB.PCI0.SBUS.SMBI) (20160108/utaddress-255) |
| ACPI: If an ACPI driver is available for this device, you should use |
| it instead of the native driver |
| |
| The reason is that this SMBI OpRegion conflicts with the PCI BAR used by |
| the SMBus driver. |
| |
| It turns out that we can install a custom SystemIO address space handler |
| for the SMBus device to intercept all accesses through that OpRegion. This |
| allows us to share the PCI BAR with the AML code if it for some reason is |
| using it. We do not expect that this OpRegion handler will ever be called |
| but if it is we print a warning and prevent all access from the SMBus |
| driver itself. |
| |
| Link: https://bugzilla.kernel.org/show_bug.cgi?id=110041 |
| Reported-by: Andy Lutomirski <luto@kernel.org> |
| Reported-by: Pali Rohár <pali.rohar@gmail.com> |
| Suggested-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> |
| Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com> |
| Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> |
| Reviewed-by: Jean Delvare <jdelvare@suse.de> |
| Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> |
| Tested-by: Pali Rohár <pali.rohar@gmail.com> |
| Tested-by: Jean Delvare <jdelvare@suse.de> |
| Signed-off-by: Wolfram Sang <wsa@the-dreams.de> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/i2c/busses/i2c-i801.c | 103 ++++++++++++++++++++++++++++++++++++++---- |
| 1 file changed, 94 insertions(+), 9 deletions(-) |
| |
| --- a/drivers/i2c/busses/i2c-i801.c |
| +++ b/drivers/i2c/busses/i2c-i801.c |
| @@ -244,6 +244,13 @@ struct i801_priv { |
| struct platform_device *mux_pdev; |
| #endif |
| struct platform_device *tco_pdev; |
| + |
| + /* |
| + * If set to true the host controller registers are reserved for |
| + * ACPI AML use. Protected by acpi_lock. |
| + */ |
| + bool acpi_reserved; |
| + struct mutex acpi_lock; |
| }; |
| |
| #define FEATURE_SMBUS_PEC (1 << 0) |
| @@ -714,9 +721,15 @@ static s32 i801_access(struct i2c_adapte |
| { |
| int hwpec; |
| int block = 0; |
| - int ret, xact = 0; |
| + int ret = 0, xact = 0; |
| struct i801_priv *priv = i2c_get_adapdata(adap); |
| |
| + mutex_lock(&priv->acpi_lock); |
| + if (priv->acpi_reserved) { |
| + mutex_unlock(&priv->acpi_lock); |
| + return -EBUSY; |
| + } |
| + |
| hwpec = (priv->features & FEATURE_SMBUS_PEC) && (flags & I2C_CLIENT_PEC) |
| && size != I2C_SMBUS_QUICK |
| && size != I2C_SMBUS_I2C_BLOCK_DATA; |
| @@ -773,7 +786,8 @@ static s32 i801_access(struct i2c_adapte |
| default: |
| dev_err(&priv->pci_dev->dev, "Unsupported transaction %d\n", |
| size); |
| - return -EOPNOTSUPP; |
| + ret = -EOPNOTSUPP; |
| + goto out; |
| } |
| |
| if (hwpec) /* enable/disable hardware PEC */ |
| @@ -796,11 +810,11 @@ static s32 i801_access(struct i2c_adapte |
| ~(SMBAUXCTL_CRC | SMBAUXCTL_E32B), SMBAUXCTL(priv)); |
| |
| if (block) |
| - return ret; |
| + goto out; |
| if (ret) |
| - return ret; |
| + goto out; |
| if ((read_write == I2C_SMBUS_WRITE) || (xact == I801_QUICK)) |
| - return 0; |
| + goto out; |
| |
| switch (xact & 0x7f) { |
| case I801_BYTE: /* Result put in SMBHSTDAT0 */ |
| @@ -812,7 +826,10 @@ static s32 i801_access(struct i2c_adapte |
| (inb_p(SMBHSTDAT1(priv)) << 8); |
| break; |
| } |
| - return 0; |
| + |
| +out: |
| + mutex_unlock(&priv->acpi_lock); |
| + return ret; |
| } |
| |
| |
| @@ -1249,6 +1266,72 @@ static void i801_add_tco(struct i801_pri |
| priv->tco_pdev = pdev; |
| } |
| |
| +#ifdef CONFIG_ACPI |
| +static acpi_status |
| +i801_acpi_io_handler(u32 function, acpi_physical_address address, u32 bits, |
| + u64 *value, void *handler_context, void *region_context) |
| +{ |
| + struct i801_priv *priv = handler_context; |
| + struct pci_dev *pdev = priv->pci_dev; |
| + 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. |
| + */ |
| + mutex_lock(&priv->acpi_lock); |
| + |
| + if (!priv->acpi_reserved) { |
| + priv->acpi_reserved = true; |
| + |
| + dev_warn(&pdev->dev, "BIOS is accessing SMBus registers\n"); |
| + dev_warn(&pdev->dev, "Driver SMBus register access inhibited\n"); |
| + } |
| + |
| + 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); |
| + |
| + mutex_unlock(&priv->acpi_lock); |
| + |
| + return status; |
| +} |
| + |
| +static int i801_acpi_probe(struct i801_priv *priv) |
| +{ |
| + struct acpi_device *adev; |
| + acpi_status status; |
| + |
| + adev = ACPI_COMPANION(&priv->pci_dev->dev); |
| + if (adev) { |
| + status = acpi_install_address_space_handler(adev->handle, |
| + ACPI_ADR_SPACE_SYSTEM_IO, i801_acpi_io_handler, |
| + NULL, priv); |
| + if (ACPI_SUCCESS(status)) |
| + return 0; |
| + } |
| + |
| + return acpi_check_resource_conflict(&priv->pci_dev->resource[SMBBAR]); |
| +} |
| + |
| +static void i801_acpi_remove(struct i801_priv *priv) |
| +{ |
| + struct acpi_device *adev; |
| + |
| + adev = ACPI_COMPANION(&priv->pci_dev->dev); |
| + if (!adev) |
| + return; |
| + |
| + acpi_remove_address_space_handler(adev->handle, |
| + ACPI_ADR_SPACE_SYSTEM_IO, i801_acpi_io_handler); |
| +} |
| +#else |
| +static inline int i801_acpi_probe(struct i801_priv *priv) { return 0; } |
| +static inline void i801_acpi_remove(struct i801_priv *priv) { } |
| +#endif |
| + |
| static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id) |
| { |
| unsigned char temp; |
| @@ -1266,6 +1349,7 @@ static int i801_probe(struct pci_dev *de |
| priv->adapter.dev.parent = &dev->dev; |
| ACPI_COMPANION_SET(&priv->adapter.dev, ACPI_COMPANION(&dev->dev)); |
| priv->adapter.retries = 3; |
| + mutex_init(&priv->acpi_lock); |
| |
| priv->pci_dev = dev; |
| switch (dev->device) { |
| @@ -1328,10 +1412,8 @@ static int i801_probe(struct pci_dev *de |
| return -ENODEV; |
| } |
| |
| - err = acpi_check_resource_conflict(&dev->resource[SMBBAR]); |
| - if (err) { |
| + if (i801_acpi_probe(priv)) |
| return -ENODEV; |
| - } |
| |
| err = pcim_iomap_regions(dev, 1 << SMBBAR, |
| dev_driver_string(&dev->dev)); |
| @@ -1340,6 +1422,7 @@ static int i801_probe(struct pci_dev *de |
| "Failed to request SMBus region 0x%lx-0x%Lx\n", |
| priv->smba, |
| (unsigned long long)pci_resource_end(dev, SMBBAR)); |
| + i801_acpi_remove(priv); |
| return err; |
| } |
| |
| @@ -1404,6 +1487,7 @@ static int i801_probe(struct pci_dev *de |
| err = i2c_add_adapter(&priv->adapter); |
| if (err) { |
| dev_err(&dev->dev, "Failed to add SMBus adapter\n"); |
| + i801_acpi_remove(priv); |
| return err; |
| } |
| |
| @@ -1422,6 +1506,7 @@ static void i801_remove(struct pci_dev * |
| |
| i801_del_mux(priv); |
| i2c_del_adapter(&priv->adapter); |
| + i801_acpi_remove(priv); |
| pci_write_config_byte(dev, SMBHSTCFG, priv->original_hstcfg); |
| |
| platform_device_unregister(priv->tco_pdev); |