diff mbox

[v5] i2c: i801: Allow ACPI SystemIO OpRegion to conflict with PCI BAR

Message ID 1463990658-53854-1-git-send-email-mika.westerberg@linux.intel.com
State Superseded
Headers show

Commit Message

Mika Westerberg May 23, 2016, 8:04 a.m. UTC
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-and-tested-by: Pali Rohár <pali.rohar@gmail.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Suggested-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Tested-by: Jean Delvare <jdelvare@suse.de>
Reviewed-by: Jean Delvare <jdelvare@suse.de>
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Cc: stable@vger.kernel.org
---
Changes to v4:
 - Use ACPI_IO_MASK to mask function to get right value if in future we get
   something else added to that field.
 - Add Reviewed/Tested-by from Jean Delvare

Changes to v3:
 - Added Tested-by from Pali Rohár (The patch did not change that much so I
   though it is still valid)
 - Return -EBUSY instead of -EIO
 - Move dev_warns() to be inside if (!priv->acpi_reserved) block
 - Remove unnecessary variable "val"
 - Do not clear priv->acpi_reserved in i801_acpi_remove()
 - Return -ENODEV if we detect conflict
 - Call i801_acpi_remove() after i2c_del_adapter().

Changes to v2:
 - Return -EIO instead of -EPERM
 - Added ACK from Rafael
 - Added Link and Reported-by tags
 - Tagged for stable inclusion

 drivers/i2c/busses/i2c-i801.c | 98 +++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 95 insertions(+), 3 deletions(-)

Comments

Benjamin Tissoires June 8, 2016, 4:29 p.m. UTC | #1
On May 23 2016 or thereabouts, Mika Westerberg wrote:
> 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-and-tested-by: Pali Rohár <pali.rohar@gmail.com>
> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> Suggested-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
> Tested-by: Jean Delvare <jdelvare@suse.de>
> Reviewed-by: Jean Delvare <jdelvare@suse.de>
> Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
> Cc: stable@vger.kernel.org
> ---
> Changes to v4:
>  - Use ACPI_IO_MASK to mask function to get right value if in future we get
>    something else added to that field.
>  - Add Reviewed/Tested-by from Jean Delvare
> 
> Changes to v3:
>  - Added Tested-by from Pali Rohár (The patch did not change that much so I
>    though it is still valid)
>  - Return -EBUSY instead of -EIO
>  - Move dev_warns() to be inside if (!priv->acpi_reserved) block
>  - Remove unnecessary variable "val"
>  - Do not clear priv->acpi_reserved in i801_acpi_remove()
>  - Return -ENODEV if we detect conflict
>  - Call i801_acpi_remove() after i2c_del_adapter().
> 
> Changes to v2:
>  - Return -EIO instead of -EPERM
>  - Added ACK from Rafael
>  - Added Link and Reported-by tags
>  - Tagged for stable inclusion
> 
>  drivers/i2c/busses/i2c-i801.c | 98 +++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 95 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c
> index 5652bf6ce9be..d613263d3f96 100644
> --- a/drivers/i2c/busses/i2c-i801.c
> +++ b/drivers/i2c/busses/i2c-i801.c
> @@ -247,6 +247,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)
> @@ -720,6 +727,12 @@ static s32 i801_access(struct i2c_adapter *adap, u16 addr,
>  	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;
> +	}
> +
>  	pm_runtime_get_sync(&priv->pci_dev->dev);
>  
>  	hwpec = (priv->features & FEATURE_SMBUS_PEC) && (flags & I2C_CLIENT_PEC)
> @@ -822,6 +835,7 @@ static s32 i801_access(struct i2c_adapter *adap, u16 addr,
>  out:
>  	pm_runtime_mark_last_busy(&priv->pci_dev->dev);
>  	pm_runtime_put_autosuspend(&priv->pci_dev->dev);
> +	mutex_unlock(&priv->acpi_lock);
>  	return ret;
>  }
>  
> @@ -1260,6 +1274,83 @@ static void i801_add_tco(struct i801_priv *priv)
>  	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");
> +
> +		/*
> +		 * BIOS is accessing the host controller so prevent it from
> +		 * suspending automatically from now on.
> +		 */
> +		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);
> +
> +	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);
> +
> +	mutex_lock(&priv->acpi_lock);
> +	if (priv->acpi_reserved)
> +		pm_runtime_put(&priv->pci_dev->dev);
> +	mutex_unlock(&priv->acpi_lock);
> +}
> +#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;
> @@ -1277,6 +1368,7 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
>  	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) {
> @@ -1339,10 +1431,9 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
>  		return -ENODEV;
>  	}
>  
> -	err = acpi_check_resource_conflict(&dev->resource[SMBBAR]);
> -	if (err) {
> +	err = i801_acpi_probe(priv);
> +	if (err)
>  		return -ENODEV;
> -	}

I'd say that once this has been set, we need to call
acpi_remove_address_space_handler() in case of failure later (in the 2
returns after).

The rest looks OK to me.

Cheers,
Benjamin

>  
>  	err = pcim_iomap_regions(dev, 1 << SMBBAR,
>  				 dev_driver_string(&dev->dev));
> @@ -1441,6 +1532,7 @@ static void i801_remove(struct pci_dev *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);
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mika Westerberg June 9, 2016, 8:15 a.m. UTC | #2
On Wed, Jun 08, 2016 at 06:29:13PM +0200, Benjamin Tissoires wrote:
> > -	err = acpi_check_resource_conflict(&dev->resource[SMBBAR]);
> > -	if (err) {
> > +	err = i801_acpi_probe(priv);
> > +	if (err)
> >  		return -ENODEV;
> > -	}
> 
> I'd say that once this has been set, we need to call
> acpi_remove_address_space_handler() in case of failure later (in the 2
> returns after).

Indeed - I wonder how many mistakes one patch can contain :-(

Let me fix this and submit yet another version.

> The rest looks OK to me.

Thanks!
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jean Delvare June 13, 2016, 9:19 a.m. UTC | #3
Hi Benjamin,

On Wed, 8 Jun 2016 18:29:13 +0200, Benjamin Tissoires wrote:
> >  static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
> >  {
> >  	unsigned char temp;
> > @@ -1277,6 +1368,7 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
> >  	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) {
> > @@ -1339,10 +1431,9 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
> >  		return -ENODEV;
> >  	}
> >  
> > -	err = acpi_check_resource_conflict(&dev->resource[SMBBAR]);
> > -	if (err) {
> > +	err = i801_acpi_probe(priv);
> > +	if (err)
> >  		return -ENODEV;
> > -	}
> 
> I'd say that once this has been set, we need to call
> acpi_remove_address_space_handler() in case of failure later (in the 2
> returns after).

Good catch, sorry for missing it during my review.

The first error return can probably be left unchanged by calling
i801_acpi_probe() after it rather than before. The second will need a
call to i801_acpi_remove(priv) for sure.
Pali Rohár June 13, 2016, 9:45 a.m. UTC | #4
On Monday 13 June 2016 11:19:46 Jean Delvare wrote:
> Hi Benjamin,
> 
> On Wed, 8 Jun 2016 18:29:13 +0200, Benjamin Tissoires wrote:
> > >  static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
> > >  {
> > >  	unsigned char temp;
> > > @@ -1277,6 +1368,7 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
> > >  	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) {
> > > @@ -1339,10 +1431,9 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
> > >  		return -ENODEV;
> > >  	}
> > >  
> > > -	err = acpi_check_resource_conflict(&dev->resource[SMBBAR]);
> > > -	if (err) {
> > > +	err = i801_acpi_probe(priv);
> > > +	if (err)
> > >  		return -ENODEV;
> > > -	}
> > 
> > I'd say that once this has been set, we need to call
> > acpi_remove_address_space_handler() in case of failure later (in the 2
> > returns after).
> 
> Good catch, sorry for missing it during my review.
> 
> The first error return can probably be left unchanged by calling
> i801_acpi_probe() after it rather than before. The second will need a
> call to i801_acpi_remove(priv) for sure.

Maybe we should call acpi_remove_address_space_handler() after first
ACPI access to driver?
Mika Westerberg June 13, 2016, 9:46 a.m. UTC | #5
On Mon, Jun 13, 2016 at 11:45:00AM +0200, Pali Rohár wrote:
> On Monday 13 June 2016 11:19:46 Jean Delvare wrote:
> > Hi Benjamin,
> > 
> > On Wed, 8 Jun 2016 18:29:13 +0200, Benjamin Tissoires wrote:
> > > >  static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
> > > >  {
> > > >  	unsigned char temp;
> > > > @@ -1277,6 +1368,7 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
> > > >  	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) {
> > > > @@ -1339,10 +1431,9 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
> > > >  		return -ENODEV;
> > > >  	}
> > > >  
> > > > -	err = acpi_check_resource_conflict(&dev->resource[SMBBAR]);
> > > > -	if (err) {
> > > > +	err = i801_acpi_probe(priv);
> > > > +	if (err)
> > > >  		return -ENODEV;
> > > > -	}
> > > 
> > > I'd say that once this has been set, we need to call
> > > acpi_remove_address_space_handler() in case of failure later (in the 2
> > > returns after).
> > 
> > Good catch, sorry for missing it during my review.
> > 
> > The first error return can probably be left unchanged by calling
> > i801_acpi_probe() after it rather than before. The second will need a
> > call to i801_acpi_remove(priv) for sure.
> 
> Maybe we should call acpi_remove_address_space_handler() after first
> ACPI access to driver?

I fixed it already in v6 which got applied by Wolfram.
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pali Rohár June 13, 2016, 9:48 a.m. UTC | #6
On Monday 13 June 2016 12:46:18 Mika Westerberg wrote:
> On Mon, Jun 13, 2016 at 11:45:00AM +0200, Pali Rohár wrote:
> > On Monday 13 June 2016 11:19:46 Jean Delvare wrote:
> > > Hi Benjamin,
> > > 
> > > On Wed, 8 Jun 2016 18:29:13 +0200, Benjamin Tissoires wrote:
> > > > >  static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
> > > > >  {
> > > > >  	unsigned char temp;
> > > > > @@ -1277,6 +1368,7 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
> > > > >  	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) {
> > > > > @@ -1339,10 +1431,9 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
> > > > >  		return -ENODEV;
> > > > >  	}
> > > > >  
> > > > > -	err = acpi_check_resource_conflict(&dev->resource[SMBBAR]);
> > > > > -	if (err) {
> > > > > +	err = i801_acpi_probe(priv);
> > > > > +	if (err)
> > > > >  		return -ENODEV;
> > > > > -	}
> > > > 
> > > > I'd say that once this has been set, we need to call
> > > > acpi_remove_address_space_handler() in case of failure later (in the 2
> > > > returns after).
> > > 
> > > Good catch, sorry for missing it during my review.
> > > 
> > > The first error return can probably be left unchanged by calling
> > > i801_acpi_probe() after it rather than before. The second will need a
> > > call to i801_acpi_remove(priv) for sure.
> > 
> > Maybe we should call acpi_remove_address_space_handler() after first
> > ACPI access to driver?
> 
> I fixed it already in v6 which got applied by Wolfram.

I mean to call acpi_remove_address_space_handler() in
i801_acpi_io_handler() after acpi_reserved is properly set.

As once acpi_reserved is set address space handler is not needed
anymore.
Mika Westerberg June 13, 2016, 9:54 a.m. UTC | #7
On Mon, Jun 13, 2016 at 11:48:30AM +0200, Pali Rohár wrote:
> I mean to call acpi_remove_address_space_handler() in
> i801_acpi_io_handler() after acpi_reserved is properly set.
> 
> As once acpi_reserved is set address space handler is not needed
> anymore.

It is still needed as we handle all AML OpRegion access in this driver
from that point forward. Unless I'm missing something.
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jean Delvare June 24, 2016, 2:12 p.m. UTC | #8
Hi Mika,

On Mon, 13 Jun 2016 12:54:04 +0300, Mika Westerberg wrote:
> On Mon, Jun 13, 2016 at 11:48:30AM +0200, Pali Rohár wrote:
> > I mean to call acpi_remove_address_space_handler() in
> > i801_acpi_io_handler() after acpi_reserved is properly set.
> > 
> > As once acpi_reserved is set address space handler is not needed
> > anymore.
> 
> It is still needed as we handle all AML OpRegion access in this driver
> from that point forward. Unless I'm missing something.

I think Pali is correct. The only purpose of handling the region is to
detect that it is being accessed so we can set priv->acpi_reserved.
Once it is set, i801_acpi_io_handler becomes transparent: it forwards
the requests without doing anything with them. The very same would
happen if we would unregister the handler at that point, but without the
extra overhead.

So while the current code does work fine, unregistering the handler
when we set priv->acpi_reserved would be more optimal.

Unless both Pali and myself are missing something, that is.
Pali Rohár June 29, 2016, 7:56 a.m. UTC | #9
On Friday 24 June 2016 16:12:38 Jean Delvare wrote:
> Hi Mika,
> 
> On Mon, 13 Jun 2016 12:54:04 +0300, Mika Westerberg wrote:
> > On Mon, Jun 13, 2016 at 11:48:30AM +0200, Pali Rohár wrote:
> > > I mean to call acpi_remove_address_space_handler() in
> > > i801_acpi_io_handler() after acpi_reserved is properly set.
> > > 
> > > As once acpi_reserved is set address space handler is not needed
> > > anymore.
> > 
> > It is still needed as we handle all AML OpRegion access in this driver
> > from that point forward. Unless I'm missing something.
> 
> I think Pali is correct. The only purpose of handling the region is to
> detect that it is being accessed so we can set priv->acpi_reserved.
> Once it is set, i801_acpi_io_handler becomes transparent: it forwards
> the requests without doing anything with them. The very same would
> happen if we would unregister the handler at that point, but without the
> extra overhead.
> 
> So while the current code does work fine, unregistering the handler
> when we set priv->acpi_reserved would be more optimal.
> 
> Unless both Pali and myself are missing something, that is.

Yes, this is what I mean...
Mika Westerberg June 29, 2016, 10:39 a.m. UTC | #10
On Fri, Jun 24, 2016 at 04:12:38PM +0200, Jean Delvare wrote:
> Hi Mika,
> 
> On Mon, 13 Jun 2016 12:54:04 +0300, Mika Westerberg wrote:
> > On Mon, Jun 13, 2016 at 11:48:30AM +0200, Pali Rohár wrote:
> > > I mean to call acpi_remove_address_space_handler() in
> > > i801_acpi_io_handler() after acpi_reserved is properly set.
> > > 
> > > As once acpi_reserved is set address space handler is not needed
> > > anymore.
> > 
> > It is still needed as we handle all AML OpRegion access in this driver
> > from that point forward. Unless I'm missing something.
> 
> I think Pali is correct. The only purpose of handling the region is to
> detect that it is being accessed so we can set priv->acpi_reserved.
> Once it is set, i801_acpi_io_handler becomes transparent: it forwards
> the requests without doing anything with them. The very same would
> happen if we would unregister the handler at that point, but without the
> extra overhead.
> 
> So while the current code does work fine, unregistering the handler
> when we set priv->acpi_reserved would be more optimal.
> 
> Unless both Pali and myself are missing something, that is.

I'm not sure unregistering the handler actually resets back to the
default handler. Besides, this patch has been already merged for a while
so it requires a followup patch on top.
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jean Delvare July 4, 2016, 8:22 a.m. UTC | #11
Hi Mika,

On Wed, 29 Jun 2016 13:39:51 +0300, Mika Westerberg wrote:
> On Fri, Jun 24, 2016 at 04:12:38PM +0200, Jean Delvare wrote:
> > I think Pali is correct. The only purpose of handling the region is to
> > detect that it is being accessed so we can set priv->acpi_reserved.
> > Once it is set, i801_acpi_io_handler becomes transparent: it forwards
> > the requests without doing anything with them. The very same would
> > happen if we would unregister the handler at that point, but without the
> > extra overhead.
> > 
> > So while the current code does work fine, unregistering the handler
> > when we set priv->acpi_reserved would be more optimal.
> > 
> > Unless both Pali and myself are missing something, that is.
> 
> I'm not sure unregistering the handler actually resets back to the
> default handler.

I'm no ACPI expert. I read the code of
acpi_remove_address_space_handler() and a few other related ACPI
functions and can't claim I understood it all. But indeed it doesn't
look like it restores the original behavior. Probably
acpi_install_address_space_handler(..., ACPI_ADR_SPACE_SYSTEM_IO,
ACPI_DEFAULT_HANDLER, ...) should be used instead.

This raises another question though: if
acpi_remove_address_space_handler() doesn't restore the previous
behavior then we shouldn't be calling it when the driver is being
unloaded either. As I understand it, it breaks the ACPI handling of the
device.

However I can't test it, as the installed handler is never called
on my system. Can anyone test unloading the i2c-i801 driver on a system
where ACPI actually accesses the device?

After looking at the ACPI code, I am no longer convinced that restoring
the default handler would improve performance. The default handler
itself (acpi_ex_system_io_space_handler) has a lot of overhead. OTOH
this makes me wonder if it is really correct to call
acpi_os_read_port() and acpi_os_write_port() directly.
acpi_ex_system_io_space_handler() calls acpi_hw_read_port() and
acpi_hw_write_port() which perform additional checks. Actually it would
seem safer to call acpi_ex_system_io_space_handler() instead... if it
was exported. Oh well.

> Besides, this patch has been already merged for a while
> so it requires a followup patch on top.

Correct, whatever we do.
Pali Rohár July 4, 2016, 2:30 p.m. UTC | #12
On Monday 04 July 2016 10:22:12 Jean Delvare wrote:
> However I can't test it, as the installed handler is never called
> on my system. Can anyone test unloading the i2c-i801 driver on a system
> where ACPI actually accesses the device?

I remember that HP EliteBooks with HDD accelerometer protection use
hp_accel ACPI driver which use that ACPI SBUS device. But I do not own
EliteBooks anymore... Maybe someone else can test? What I found probably
find are ACPI DSDT dumps from those machines, but do not know if that
can help...
Mika Westerberg July 5, 2016, 10:14 a.m. UTC | #13
On Mon, Jul 04, 2016 at 10:22:12AM +0200, Jean Delvare wrote:
> Hi Mika,
> 
> On Wed, 29 Jun 2016 13:39:51 +0300, Mika Westerberg wrote:
> > On Fri, Jun 24, 2016 at 04:12:38PM +0200, Jean Delvare wrote:
> > > I think Pali is correct. The only purpose of handling the region is to
> > > detect that it is being accessed so we can set priv->acpi_reserved.
> > > Once it is set, i801_acpi_io_handler becomes transparent: it forwards
> > > the requests without doing anything with them. The very same would
> > > happen if we would unregister the handler at that point, but without the
> > > extra overhead.
> > > 
> > > So while the current code does work fine, unregistering the handler
> > > when we set priv->acpi_reserved would be more optimal.
> > > 
> > > Unless both Pali and myself are missing something, that is.
> > 
> > I'm not sure unregistering the handler actually resets back to the
> > default handler.
> 
> I'm no ACPI expert. I read the code of
> acpi_remove_address_space_handler() and a few other related ACPI
> functions and can't claim I understood it all. But indeed it doesn't
> look like it restores the original behavior. Probably
> acpi_install_address_space_handler(..., ACPI_ADR_SPACE_SYSTEM_IO,
> ACPI_DEFAULT_HANDLER, ...) should be used instead.
> 
> This raises another question though: if
> acpi_remove_address_space_handler() doesn't restore the previous
> behavior then we shouldn't be calling it when the driver is being
> unloaded either. As I understand it, it breaks the ACPI handling of the
> device.
> 
> However I can't test it, as the installed handler is never called
> on my system. Can anyone test unloading the i2c-i801 driver on a system
> where ACPI actually accesses the device?

The whole point of this patch is that we expect that nobody never uses
that OpRegion. I'm 99% sure you don't find a single machine where it is
actually in use.

The fallback code is there just to be sure we do not blow things up if
it turns out some machine is using that.

I think the best we can do is to lock down the module (prevent it from
unloading) if we notice that the OpRegion is used.
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pali Rohár July 5, 2016, 11:30 a.m. UTC | #14
On Tuesday 05 July 2016 12:14:55 Mika Westerberg wrote:
> The whole point of this patch is that we expect that nobody never
> uses that OpRegion. I'm 99% sure you don't find a single machine
> where it is actually in use.

HP EliteBook 8460p uses it for sure! Here are DSDT snips:


                Method (\_SB.PCI0.LPCB.SMAB, 3, Serialized)
                {
                    If (LEqual (And (Arg0, 0x01), 0x00))
                    {
                        Store (0x01, Local0)
                        Store (\_SB.PCI0.SBUS.SWRB (Arg0, Arg1, Arg2), Local1)
                        If (Local1)
                        {
                            Store (0x00, Local0)
                        }
                    }
                    Else
                    {
                        Store (\_SB.PCI0.SBUS.SRDB (Arg0, Arg1), Local0)
                    }

                    Return (Local0)
                }

...

                Method (ALRD, 1, Serialized)
                {
                    Store (\_SB.PCI0.LPCB.SMAB (0x33, Arg0, 0x00), Local0)
                    Return (Local0)
                }

                Method (ALWR, 2, Serialized)
                {
                    Store (\_SB.PCI0.LPCB.SMAB (0x32, Arg0, Arg1), Local0)
                    Return (Local0)
                }


And ALRD and ALWR methods are used by hp_accel.ko kernel driver.
Mika Westerberg July 5, 2016, 11:51 a.m. UTC | #15
On Tue, Jul 05, 2016 at 01:30:23PM +0200, Pali Rohár wrote:
> On Tuesday 05 July 2016 12:14:55 Mika Westerberg wrote:
> > The whole point of this patch is that we expect that nobody never
> > uses that OpRegion. I'm 99% sure you don't find a single machine
> > where it is actually in use.
> 
> HP EliteBook 8460p uses it for sure! Here are DSDT snips:
> 
> 
>                 Method (\_SB.PCI0.LPCB.SMAB, 3, Serialized)
>                 {
>                     If (LEqual (And (Arg0, 0x01), 0x00))
>                     {
>                         Store (0x01, Local0)
>                         Store (\_SB.PCI0.SBUS.SWRB (Arg0, Arg1, Arg2), Local1)
>                         If (Local1)
>                         {
>                             Store (0x00, Local0)
>                         }
>                     }
>                     Else
>                     {
>                         Store (\_SB.PCI0.SBUS.SRDB (Arg0, Arg1), Local0)
>                     }
> 
>                     Return (Local0)
>                 }
> 

Crap, well that is in that 1% then ;-)

> ...
> 
>                 Method (ALRD, 1, Serialized)
>                 {
>                     Store (\_SB.PCI0.LPCB.SMAB (0x33, Arg0, 0x00), Local0)
>                     Return (Local0)
>                 }
> 
>                 Method (ALWR, 2, Serialized)
>                 {
>                     Store (\_SB.PCI0.LPCB.SMAB (0x32, Arg0, Arg1), Local0)
>                     Return (Local0)
>                 }
> 
> 
> And ALRD and ALWR methods are used by hp_accel.ko kernel driver.

So are you able to test what happens when you unload the driver? I think
the safest thing to do is that we just pin the driver in the kernel once
we notice the OpRegion is being accessed. Does anyone else have better
ideas?
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pali Rohár July 5, 2016, 11:56 a.m. UTC | #16
On Tuesday 05 July 2016 13:51:42 Mika Westerberg wrote:
> On Tue, Jul 05, 2016 at 01:30:23PM +0200, Pali Rohár wrote:
> > On Tuesday 05 July 2016 12:14:55 Mika Westerberg wrote:
> > > The whole point of this patch is that we expect that nobody never
> > > uses that OpRegion. I'm 99% sure you don't find a single machine
> > > where it is actually in use.
> > 
> > HP EliteBook 8460p uses it for sure! Here are DSDT snips:
> >                 Method (\_SB.PCI0.LPCB.SMAB, 3, Serialized)
> >                 {
> >                 
> >                     If (LEqual (And (Arg0, 0x01), 0x00))
> >                     {
> >                     
> >                         Store (0x01, Local0)
> >                         Store (\_SB.PCI0.SBUS.SWRB (Arg0, Arg1,
> >                         Arg2), Local1) If (Local1)
> >                         {
> >                         
> >                             Store (0x00, Local0)
> >                         
> >                         }
> >                     
> >                     }
> >                     Else
> >                     {
> >                     
> >                         Store (\_SB.PCI0.SBUS.SRDB (Arg0, Arg1),
> >                         Local0)
> >                     
> >                     }
> >                     
> >                     Return (Local0)
> >                 
> >                 }
> 
> Crap, well that is in that 1% then ;-)

I bet that every HP notebook with accelerometer which is used by 
hp_accel.ko driver is affected by this problem. And then it will be more 
then 1% :-)

> > ...
> > 
> >                 Method (ALRD, 1, Serialized)
> >                 {
> >                 
> >                     Store (\_SB.PCI0.LPCB.SMAB (0x33, Arg0, 0x00),
> >                     Local0) Return (Local0)
> >                 
> >                 }
> >                 
> >                 Method (ALWR, 2, Serialized)
> >                 {
> >                 
> >                     Store (\_SB.PCI0.LPCB.SMAB (0x32, Arg0, Arg1),
> >                     Local0) Return (Local0)
> >                 
> >                 }
> > 
> > And ALRD and ALWR methods are used by hp_accel.ko kernel driver.
> 
> So are you able to test what happens when you unload the driver?

As I wrote in previous email, I do not own these EliteBooks anymore, so 
cannot test it. Just have DSDT dump...
Pali Rohár July 5, 2016, noon UTC | #17
On Tuesday 05 July 2016 13:56:58 Pali Rohár wrote:
> On Tuesday 05 July 2016 13:51:42 Mika Westerberg wrote:
> > So are you able to test what happens when you unload the driver?
> 
> As I wrote in previous email, I do not own these EliteBooks anymore,
> so cannot test it. Just have DSDT dump...

What about contacting last contributors to hp_accel.c driver? They 
probably could test i801 changes if accelerometer still works.
Mika Westerberg July 5, 2016, 2:31 p.m. UTC | #18
On Tue, Jul 05, 2016 at 02:00:58PM +0200, Pali Rohár wrote:
> On Tuesday 05 July 2016 13:56:58 Pali Rohár wrote:
> > On Tuesday 05 July 2016 13:51:42 Mika Westerberg wrote:
> > > So are you able to test what happens when you unload the driver?
> > 
> > As I wrote in previous email, I do not own these EliteBooks anymore,
> > so cannot test it. Just have DSDT dump...
> 
> What about contacting last contributors to hp_accel.c driver? They 
> probably could test i801 changes if accelerometer still works.

Good idea.

Added Martin and Dominique who did the last additions to that driver
(hp_accel.c).

Do you guys still have your HP machines around? If yes, maybe you can
try v4.7-rc3+ (it should include commit a7ae81952cda ("i2c: i801: Allow
ACPI SystemIO OpRegion to conflict with PCI BAR")) so that you unload
i2c-i801.ko and see if accelerometer still works?
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pali Rohár July 14, 2016, 11:52 a.m. UTC | #19
On Tuesday 05 July 2016 14:00:58 Pali Rohár wrote:
> On Tuesday 05 July 2016 13:56:58 Pali Rohár wrote:
> > On Tuesday 05 July 2016 13:51:42 Mika Westerberg wrote:
> > > So are you able to test what happens when you unload the driver?
> > 
> > As I wrote in previous email, I do not own these EliteBooks anymore,
> > so cannot test it. Just have DSDT dump...
> 
> What about contacting last contributors to hp_accel.c driver? They 
> probably could test i801 changes if accelerometer still works.

Or another option how to test this: Use acpi_call module which exports
file /proc/acpi/call which can be used to issue ACPI method call from
userspace.

If there are problems, there will be errors in dmesg or return value
from /proc/acpi/call is some error...
Rafael J. Wysocki July 14, 2016, 2:20 p.m. UTC | #20
On Thursday, July 14, 2016 01:52:18 PM Pali Rohár wrote:
> On Tuesday 05 July 2016 14:00:58 Pali Rohár wrote:
> > On Tuesday 05 July 2016 13:56:58 Pali Rohár wrote:
> > > On Tuesday 05 July 2016 13:51:42 Mika Westerberg wrote:
> > > > So are you able to test what happens when you unload the driver?
> > > 
> > > As I wrote in previous email, I do not own these EliteBooks anymore,
> > > so cannot test it. Just have DSDT dump...
> > 
> > What about contacting last contributors to hp_accel.c driver? They 
> > probably could test i801 changes if accelerometer still works.
> 
> Or another option how to test this: Use acpi_call module which exports
> file /proc/acpi/call which can be used to issue ACPI method call from
> userspace.
> 
> If there are problems, there will be errors in dmesg or return value
> from /proc/acpi/call is some error...

That is plain dangerous, because some objects may not be evaulated without
preparation.

We had something like that before and dropped it.

We have an AML debugger in the kernel now, though, which in principle may
be used for such diagnostics I think.

Thanks,
Rafael

--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Martin Vajnar July 24, 2016, 10:08 a.m. UTC | #21
2016-07-05 16:31 GMT+02:00 Mika Westerberg <mika.westerberg@linux.intel.com>:
> On Tue, Jul 05, 2016 at 02:00:58PM +0200, Pali Rohár wrote:
>> On Tuesday 05 July 2016 13:56:58 Pali Rohár wrote:
>> > On Tuesday 05 July 2016 13:51:42 Mika Westerberg wrote:
>> > > So are you able to test what happens when you unload the driver?
>> >
>> > As I wrote in previous email, I do not own these EliteBooks anymore,
>> > so cannot test it. Just have DSDT dump...
>>
>> What about contacting last contributors to hp_accel.c driver? They
>> probably could test i801 changes if accelerometer still works.
>
> Good idea.
>
> Added Martin and Dominique who did the last additions to that driver
> (hp_accel.c).
>
> Do you guys still have your HP machines around? If yes, maybe you can
> try v4.7-rc3+ (it should include commit a7ae81952cda ("i2c: i801: Allow
> ACPI SystemIO OpRegion to conflict with PCI BAR")) so that you unload
> i2c-i801.ko and see if accelerometer still works?

I still have the HP ProBook 440 G3. I used v4.7-rc7 for the test. I
tested all combinations of loading/unloading both hp_accel and
i2c_i801 modules and whenever the hp_accel was loaded the
accelerometer worked fine.

Regards,
-Martin Vajnar
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pali Rohár July 25, 2016, 10:19 a.m. UTC | #22
On Sunday 24 July 2016 12:08:25 Martin Vajnar wrote:
> 2016-07-05 16:31 GMT+02:00 Mika Westerberg <mika.westerberg@linux.intel.com>:
> > On Tue, Jul 05, 2016 at 02:00:58PM +0200, Pali Rohár wrote:
> >> On Tuesday 05 July 2016 13:56:58 Pali Rohár wrote:
> >> > On Tuesday 05 July 2016 13:51:42 Mika Westerberg wrote:
> >> > > So are you able to test what happens when you unload the driver?
> >> >
> >> > As I wrote in previous email, I do not own these EliteBooks anymore,
> >> > so cannot test it. Just have DSDT dump...
> >>
> >> What about contacting last contributors to hp_accel.c driver? They
> >> probably could test i801 changes if accelerometer still works.
> >
> > Good idea.
> >
> > Added Martin and Dominique who did the last additions to that driver
> > (hp_accel.c).
> >
> > Do you guys still have your HP machines around? If yes, maybe you can
> > try v4.7-rc3+ (it should include commit a7ae81952cda ("i2c: i801: Allow
> > ACPI SystemIO OpRegion to conflict with PCI BAR")) so that you unload
> > i2c-i801.ko and see if accelerometer still works?
> 
> I still have the HP ProBook 440 G3. I used v4.7-rc7 for the test. I
> tested all combinations of loading/unloading both hp_accel and
> i2c_i801 modules and whenever the hp_accel was loaded the
> accelerometer worked fine.
> 
> Regards,
> -Martin Vajnar

Great! Thank you for testing.
Pali Rohár July 25, 2016, 10:22 a.m. UTC | #23
On Monday 04 July 2016 10:22:12 Jean Delvare wrote:
> Hi Mika,
> 
> On Wed, 29 Jun 2016 13:39:51 +0300, Mika Westerberg wrote:
> > On Fri, Jun 24, 2016 at 04:12:38PM +0200, Jean Delvare wrote:
> > > I think Pali is correct. The only purpose of handling the region is to
> > > detect that it is being accessed so we can set priv->acpi_reserved.
> > > Once it is set, i801_acpi_io_handler becomes transparent: it forwards
> > > the requests without doing anything with them. The very same would
> > > happen if we would unregister the handler at that point, but without the
> > > extra overhead.
> > > 
> > > So while the current code does work fine, unregistering the handler
> > > when we set priv->acpi_reserved would be more optimal.
> > > 
> > > Unless both Pali and myself are missing something, that is.
> > 
> > I'm not sure unregistering the handler actually resets back to the
> > default handler.
> 
> I'm no ACPI expert. I read the code of
> acpi_remove_address_space_handler() and a few other related ACPI
> functions and can't claim I understood it all. But indeed it doesn't
> look like it restores the original behavior. Probably
> acpi_install_address_space_handler(..., ACPI_ADR_SPACE_SYSTEM_IO,
> ACPI_DEFAULT_HANDLER, ...) should be used instead.
> 
> This raises another question though: if
> acpi_remove_address_space_handler() doesn't restore the previous
> behavior then we shouldn't be calling it when the driver is being
> unloaded either. As I understand it, it breaks the ACPI handling of the
> device.
> 
> However I can't test it, as the installed handler is never called
> on my system. Can anyone test unloading the i2c-i801 driver on a system
> where ACPI actually accesses the device?
> 
> After looking at the ACPI code, I am no longer convinced that restoring
> the default handler would improve performance. The default handler
> itself (acpi_ex_system_io_space_handler) has a lot of overhead. OTOH
> this makes me wonder if it is really correct to call
> acpi_os_read_port() and acpi_os_write_port() directly.
> acpi_ex_system_io_space_handler() calls acpi_hw_read_port() and
> acpi_hw_write_port() which perform additional checks. Actually it would
> seem safer to call acpi_ex_system_io_space_handler() instead... if it
> was exported. Oh well.
> 
> > Besides, this patch has been already merged for a while
> > so it requires a followup patch on top.
> 
> Correct, whatever we do.
> 

Now Martin Vajnar confirmed that accelerometer on his notebook working
fine with that patch. But it does not mean that we should not fix
address space handler code in i801 correctly...

Can some ACPI expert look at it? Jean already wrote some useful
informations.
diff mbox

Patch

diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c
index 5652bf6ce9be..d613263d3f96 100644
--- a/drivers/i2c/busses/i2c-i801.c
+++ b/drivers/i2c/busses/i2c-i801.c
@@ -247,6 +247,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)
@@ -720,6 +727,12 @@  static s32 i801_access(struct i2c_adapter *adap, u16 addr,
 	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;
+	}
+
 	pm_runtime_get_sync(&priv->pci_dev->dev);
 
 	hwpec = (priv->features & FEATURE_SMBUS_PEC) && (flags & I2C_CLIENT_PEC)
@@ -822,6 +835,7 @@  static s32 i801_access(struct i2c_adapter *adap, u16 addr,
 out:
 	pm_runtime_mark_last_busy(&priv->pci_dev->dev);
 	pm_runtime_put_autosuspend(&priv->pci_dev->dev);
+	mutex_unlock(&priv->acpi_lock);
 	return ret;
 }
 
@@ -1260,6 +1274,83 @@  static void i801_add_tco(struct i801_priv *priv)
 	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");
+
+		/*
+		 * BIOS is accessing the host controller so prevent it from
+		 * suspending automatically from now on.
+		 */
+		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);
+
+	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);
+
+	mutex_lock(&priv->acpi_lock);
+	if (priv->acpi_reserved)
+		pm_runtime_put(&priv->pci_dev->dev);
+	mutex_unlock(&priv->acpi_lock);
+}
+#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;
@@ -1277,6 +1368,7 @@  static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	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) {
@@ -1339,10 +1431,9 @@  static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
 		return -ENODEV;
 	}
 
-	err = acpi_check_resource_conflict(&dev->resource[SMBBAR]);
-	if (err) {
+	err = i801_acpi_probe(priv);
+	if (err)
 		return -ENODEV;
-	}
 
 	err = pcim_iomap_regions(dev, 1 << SMBBAR,
 				 dev_driver_string(&dev->dev));
@@ -1441,6 +1532,7 @@  static void i801_remove(struct pci_dev *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);