Skip to contentSkip to conversation

pci/pciehp: Allow polling/irq mode to be decided on a per-port basis

PATCH In review Rajat Jain 4 participants 6 messages Mar 31, 2014 – Apr 25, 2014 PCI

lore
Share & tools
lore

Rajat Jain

author
#0 lore raw

PATCH 1/1 pci/pciehp: Allow polling/irq mode to be decided on a per-port basis

Today, there is a global pciehp_poll_mode module parameter using which either _all_ the hot-pluggable ports are to use polling, or _all_ the ports are to use interrupts.

In a system where a certain port has IRQ issues, today the only option is to use the parameter that converts ALL the ports to use polling mode. This is not good, and hence this patch intruduces a bit field that can be set using a PCI quirk that indicates that polling should always be used for this particular PCIe port. The remaining ports can still hoose to continue to operate in whatever mode they wish to.

---
 drivers/pci/hotplug/pciehp.h     |    1 +
 drivers/pci/hotplug/pciehp_hpc.c |   16 ++++++++++------
 include/linux/pci.h              |    1 +
 3 files changed, 12 insertions(+), 6 deletions(-)
drivers/pci/hotplug/pciehp.h +1 -0
index d208791..753a3b4 100644
@@ -98,6 +98,7 @@ struct controller {
 	unsigned int no_cmd_complete:1;
 	unsigned int link_active_reporting:1;
 	unsigned int notification_enabled:1;
+	unsigned int use_polling:1;	/* Always uses polling for this slot */
 	unsigned int power_fault_detected;
 };
 
drivers/pci/hotplug/pciehp_hpc.c +10 -6
index d7d058f..d210d23 100644
@@ -82,7 +82,7 @@ static inline int pciehp_request_irq(struct controller *ctrl)
 	int retval, irq = ctrl->pcie->irq;
 
 	/* Install interrupt polling timer. Start with 10 sec delay */
-	if (pciehp_poll_mode) {
+	if (ctrl->use_polling) {
 		init_timer(&ctrl->poll_timer);
 		start_int_poll_timer(ctrl, 10);
 		return 0;
@@ -98,7 +98,7 @@ static inline int pciehp_request_irq(struct controller *ctrl)
 
 static inline void pciehp_free_irq(struct controller *ctrl)
 {
-	if (pciehp_poll_mode)
+	if (ctrl->use_polling)
 		del_timer_sync(&ctrl->poll_timer);
 	else
 		free_irq(ctrl->pcie->irq, ctrl);
@@ -131,7 +131,7 @@ static int pcie_poll_cmd(struct controller *ctrl)
 
 static void pcie_wait_cmd(struct controller *ctrl, int poll)
 {
-	unsigned int msecs = pciehp_poll_mode ? 2500 : 1000;
+	unsigned int msecs = ctrl->use_polling ? 2500 : 1000;
 	unsigned long timeout = msecs_to_jiffies(msecs);
 	int rc;
 
@@ -595,7 +595,7 @@ void pcie_enable_notification(struct controller *ctrl)
 		cmd |= PCI_EXP_SLTCTL_PDCE;
 	if (MRL_SENS(ctrl))
 		cmd |= PCI_EXP_SLTCTL_MRLSCE;
-	if (!pciehp_poll_mode)
+	if (!ctrl->use_polling)
 		cmd |= PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_CCIE;
 
 	mask = (PCI_EXP_SLTCTL_PDCE | PCI_EXP_SLTCTL_ABPE |
@@ -642,14 +642,14 @@ int pciehp_reset_slot(struct slot *slot, int probe)
 	stat_mask |= PCI_EXP_SLTSTA_DLLSC;
 
 	pcie_write_cmd(ctrl, 0, ctrl_mask);
-	if (pciehp_poll_mode)
+	if (ctrl->use_polling)
 		del_timer_sync(&ctrl->poll_timer);
 
 	pci_reset_bridge_secondary_bus(ctrl->pcie->port);
 
 	pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, stat_mask);
 	pcie_write_cmd(ctrl, ctrl_mask, ctrl_mask);
-	if (pciehp_poll_mode)
+	if (ctrl->use_polling)
 		int_poll_timeout(ctrl->poll_timer.data);
 
 	return 0;
@@ -789,6 +789,10 @@ struct controller *pcie_init(struct pcie_device *dev)
                 ctrl_dbg(ctrl, "Link Active Reporting supported\n");
                 ctrl->link_active_reporting = 1;
         }
+	if (pciehp_poll_mode || dev->port->hotplug_polling) {
+		ctrl_info(ctrl, "will use polling\n");
+		ctrl->use_polling = 1;
+	}
 
 	/* Clear all remaining event bits in Slot Status register */
 	pcie_capability_write_word(pdev, PCI_EXP_SLTSTA,
include/linux/pci.h +1 -0
index a13d682..b2ec72e 100644
@@ -336,6 +336,7 @@ struct pci_dev {
 	unsigned int	is_virtfn:1;
 	unsigned int	reset_fn:1;
 	unsigned int    is_hotplug_bridge:1;
+	unsigned int    hotplug_polling:1; /* Port uses polling for hotplug */
 	unsigned int    __aer_firmware_first_valid:1;
 	unsigned int	__aer_firmware_first:1;
 	unsigned int	broken_intx_masking:1;
Signature · 1 line
1.7.9.5
#1 lore raw

Hello Bjorn,

Was wanting to check your interest on up streaming this one. This one is fairly self-contained and simple, and allows to use polling on a per-port basis.

Please let me know you have any comments.

Thanks,

Rajat

Quoted 124 lines

-----Original Message-----
From: Rajat Jain [mailto:[email protected]]
Sent: Monday, March 31, 2014 4:52 PM
To: Bjorn Helgaas; [email protected]; [email protected]
Cc: Rajat Jain; Guenter Roeck
Subject: [PATCH] pci/pciehp: Allow polling/irq mode to be decided on a per- port basis

Today, there is a global pciehp_poll_mode module parameter using which either _all_ the hot-pluggable ports are to use polling, or _all_ the ports are to use interrupts.

In a system where a certain port has IRQ issues, today the only option is to use the parameter that converts ALL the ports to use polling mode. This is not good, and hence this patch intruduces a bit field that can be set using a PCI quirk that indicates that polling should always be used for this particular PCIe port. The remaining ports can still hoose to continue to operate in whatever mode they wish to.

---
 drivers/pci/hotplug/pciehp.h     |    1 +
 drivers/pci/hotplug/pciehp_hpc.c |   16 ++++++++++------
 include/linux/pci.h              |    1 +
 3 files changed, 12 insertions(+), 6 deletions(-)
drivers/pci/hotplug/pciehp.h +1 -0
index d208791..753a3b4 100644
@@ -98,6 +98,7 @@ struct controller {
 	unsigned int no_cmd_complete:1;
 	unsigned int link_active_reporting:1;
 	unsigned int notification_enabled:1;
+	unsigned int use_polling:1;	/* Always uses polling for this slot */
 	unsigned int power_fault_detected;
 };
 
drivers/pci/hotplug/pciehp_hpc.c +1 -1
index d7d058f..d210d23 100644
@@ -82,7 +82,7 @@ static inline int pciehp_request_irq(struct controller
 *ctrl)
 	int retval, irq = ctrl->pcie->irq;
 
 	/* Install interrupt polling timer. Start with 10 sec delay */
-	if (pciehp_poll_mode) {
+	if (ctrl->use_polling) {
 		init_timer(&ctrl->poll_timer);
 		start_int_poll_timer(ctrl, 10);
drivers/pci/hotplug/pciehp_hpc.c +3 -3
 		return 0;
@@ -98,7 +98,7 @@ static inline int pciehp_request_irq(struct controller
 *ctrl)
 
 static inline void pciehp_free_irq(struct controller *ctrl)  {
-	if (pciehp_poll_mode)
+	if (ctrl->use_polling)
 		del_timer_sync(&ctrl->poll_timer);
 	else
 		free_irq(ctrl->pcie->irq, ctrl);
@@ -131,7 +131,7 @@ static int pcie_poll_cmd(struct controller *ctrl)
 
 static void pcie_wait_cmd(struct controller *ctrl, int poll)  {
-	unsigned int msecs = pciehp_poll_mode ? 2500 : 1000;
+	unsigned int msecs = ctrl->use_polling ? 2500 : 1000;
 	unsigned long timeout = msecs_to_jiffies(msecs);
 	int rc;
 
@@ -595,7 +595,7 @@ void pcie_enable_notification(struct controller *ctrl)
 		cmd |= PCI_EXP_SLTCTL_PDCE;
 	if (MRL_SENS(ctrl))
 		cmd |= PCI_EXP_SLTCTL_MRLSCE;
-	if (!pciehp_poll_mode)
+	if (!ctrl->use_polling)
 		cmd |= PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_CCIE;
 
 	mask = (PCI_EXP_SLTCTL_PDCE | PCI_EXP_SLTCTL_ABPE | @@ -

642,14 +642,14 @@ int pciehp_reset_slot(struct slot *slot, int probe)

drivers/pci/hotplug/pciehp_hpc.c +6 -2
 	stat_mask |= PCI_EXP_SLTSTA_DLLSC;
 
 	pcie_write_cmd(ctrl, 0, ctrl_mask);
-	if (pciehp_poll_mode)
+	if (ctrl->use_polling)
 		del_timer_sync(&ctrl->poll_timer);
 
 	pci_reset_bridge_secondary_bus(ctrl->pcie->port);
 
 	pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, stat_mask);
 	pcie_write_cmd(ctrl, ctrl_mask, ctrl_mask);
-	if (pciehp_poll_mode)
+	if (ctrl->use_polling)
 		int_poll_timeout(ctrl->poll_timer.data);
 
 	return 0;
@@ -789,6 +789,10 @@ struct controller *pcie_init(struct pcie_device *dev)
                 ctrl_dbg(ctrl, "Link Active Reporting supported\n");
                 ctrl->link_active_reporting = 1;
         }
+	if (pciehp_poll_mode || dev->port->hotplug_polling) {
+		ctrl_info(ctrl, "will use polling\n");
+		ctrl->use_polling = 1;
+	}
 
 	/* Clear all remaining event bits in Slot Status register */
 	pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, diff --git

a/include/linux/pci.h b/include/linux/pci.h index a13d682..b2ec72e 100644

include/linux/pci.h +1 -1
@@ -336,6 +336,7 @@ struct pci_dev {
 	unsigned int	is_virtfn:1;
 	unsigned int	reset_fn:1;
 	unsigned int    is_hotplug_bridge:1;
+	unsigned int    hotplug_polling:1; /* Port uses polling for hotplug */
 	unsigned int    __aer_firmware_first_valid:1;
 	unsigned int	__aer_firmware_first:1;
 	unsigned int	broken_intx_masking:1;
--

1.7.9.5

maintainer
#2 lore raw
In reply to Rajat Jain
Quoted 11 lines · Rajat Jain

Today, there is a global pciehp_poll_mode module parameter using which either _all_ the hot-pluggable ports are to use polling, or _all_ the ports are to use interrupts.

In a system where a certain port has IRQ issues, today the only option is to use the parameter that converts ALL the ports to use polling mode. This is not good, and hence this patch intruduces a bit field that can be set using a PCI quirk that indicates that polling should always be used for this particular PCIe port. The remaining ports can still hoose to continue to operate in whatever mode they wish to.

Quoted 3 lines

I'm willing to merge this, but I'd prefer to merge it along with a quirk that actually sets dev->hotplug_polling. Otherwise it's dead code and I'll have no way to tell whether we need to keep it.

Bjorn

Quoted 101 lines
---
 drivers/pci/hotplug/pciehp.h     |    1 +
 drivers/pci/hotplug/pciehp_hpc.c |   16 ++++++++++------
 include/linux/pci.h              |    1 +
 3 files changed, 12 insertions(+), 6 deletions(-)
drivers/pci/hotplug/pciehp.h +1 -0
index d208791..753a3b4 100644
@@ -98,6 +98,7 @@ struct controller {
 	unsigned int no_cmd_complete:1;
 	unsigned int link_active_reporting:1;
 	unsigned int notification_enabled:1;
+	unsigned int use_polling:1;	/* Always uses polling for this slot */
 	unsigned int power_fault_detected;
 };
 
drivers/pci/hotplug/pciehp_hpc.c +10 -6
index d7d058f..d210d23 100644
@@ -82,7 +82,7 @@ static inline int pciehp_request_irq(struct controller *ctrl)
 	int retval, irq = ctrl->pcie->irq;
 
 	/* Install interrupt polling timer. Start with 10 sec delay */
-	if (pciehp_poll_mode) {
+	if (ctrl->use_polling) {
 		init_timer(&ctrl->poll_timer);
 		start_int_poll_timer(ctrl, 10);
 		return 0;
@@ -98,7 +98,7 @@ static inline int pciehp_request_irq(struct controller *ctrl)
 
 static inline void pciehp_free_irq(struct controller *ctrl)
 {
-	if (pciehp_poll_mode)
+	if (ctrl->use_polling)
 		del_timer_sync(&ctrl->poll_timer);
 	else
 		free_irq(ctrl->pcie->irq, ctrl);
@@ -131,7 +131,7 @@ static int pcie_poll_cmd(struct controller *ctrl)
 
 static void pcie_wait_cmd(struct controller *ctrl, int poll)
 {
-	unsigned int msecs = pciehp_poll_mode ? 2500 : 1000;
+	unsigned int msecs = ctrl->use_polling ? 2500 : 1000;
 	unsigned long timeout = msecs_to_jiffies(msecs);
 	int rc;
 
@@ -595,7 +595,7 @@ void pcie_enable_notification(struct controller *ctrl)
 		cmd |= PCI_EXP_SLTCTL_PDCE;
 	if (MRL_SENS(ctrl))
 		cmd |= PCI_EXP_SLTCTL_MRLSCE;
-	if (!pciehp_poll_mode)
+	if (!ctrl->use_polling)
 		cmd |= PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_CCIE;
 
 	mask = (PCI_EXP_SLTCTL_PDCE | PCI_EXP_SLTCTL_ABPE |
@@ -642,14 +642,14 @@ int pciehp_reset_slot(struct slot *slot, int probe)
 	stat_mask |= PCI_EXP_SLTSTA_DLLSC;
 
 	pcie_write_cmd(ctrl, 0, ctrl_mask);
-	if (pciehp_poll_mode)
+	if (ctrl->use_polling)
 		del_timer_sync(&ctrl->poll_timer);
 
 	pci_reset_bridge_secondary_bus(ctrl->pcie->port);
 
 	pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, stat_mask);
 	pcie_write_cmd(ctrl, ctrl_mask, ctrl_mask);
-	if (pciehp_poll_mode)
+	if (ctrl->use_polling)
 		int_poll_timeout(ctrl->poll_timer.data);
 
 	return 0;
@@ -789,6 +789,10 @@ struct controller *pcie_init(struct pcie_device *dev)
                 ctrl_dbg(ctrl, "Link Active Reporting supported\n");
                 ctrl->link_active_reporting = 1;
         }
+	if (pciehp_poll_mode || dev->port->hotplug_polling) {
+		ctrl_info(ctrl, "will use polling\n");
+		ctrl->use_polling = 1;
+	}
 
 	/* Clear all remaining event bits in Slot Status register */
 	pcie_capability_write_word(pdev, PCI_EXP_SLTSTA,
include/linux/pci.h +1 -0
index a13d682..b2ec72e 100644
@@ -336,6 +336,7 @@ struct pci_dev {
 	unsigned int	is_virtfn:1;
 	unsigned int	reset_fn:1;
 	unsigned int    is_hotplug_bridge:1;
+	unsigned int    hotplug_polling:1; /* Port uses polling for hotplug */
 	unsigned int    __aer_firmware_first_valid:1;
 	unsigned int	__aer_firmware_first:1;
 	unsigned int	broken_intx_masking:1;
Signature · 1 line
1.7.9.5
#3 lore raw
Quoted 27 lines

-----Original Message-----
From: Bjorn Helgaas [mailto:[email protected]]
Sent: Thursday, April 24, 2014 2:31 PM
To: Rajat Jain
Cc: [email protected]; [email protected]; Rajat Jain; Guenter Roeck
Subject: Re: [PATCH] pci/pciehp: Allow polling/irq mode to be decided on a per-port basis

Quoted 1 line · Rajat Jain

Today, there is a global pciehp_poll_mode module parameter using

which

Quoted 4 lines

either _all_ the hot-pluggable ports are to use polling, or _all_ the ports are to use interrupts.

In a system where a certain port has IRQ issues, today the only

option

Quoted 1 line

is to use the parameter that converts ALL the ports to use polling

mode.

Quoted 1 line

This is not good, and hence this patch intruduces a bit field that

can

Quoted 4 lines

be set using a PCI quirk that indicates that polling should always be used for this particular PCIe port. The remaining ports can still hoose to continue to operate in whatever mode they wish to.

Quoted 8 lines
Quoted 3 lines

I'm willing to merge this, but I'd prefer to merge it along with a quirk that actually sets dev->hotplug_polling. Otherwise it's dead code and I'll have no way to tell whether we need to keep it.

Bjorn,

what would be the proper location for such a quirk ? We use it to help simulating hotplug support on an IDT PES12NT3. The code is a bit more invasive than just the quirk itself, since it also needs to touch link and slot status registers, so quirks.c doesn't seem appropriate.

drivers/pci/pes12nt3.c, maybe, with a separate configuration option ? Or in the hotplug directory ?

Guenter

maintainer
#4 lore raw
Quoted 37 lines · Guenter Roeck
Quoted 27 lines

-----Original Message-----
From: Bjorn Helgaas [mailto:[email protected]]
Sent: Thursday, April 24, 2014 2:31 PM
To: Rajat Jain
Cc: [email protected]; [email protected]; Rajat Jain; Guenter Roeck
Subject: Re: [PATCH] pci/pciehp: Allow polling/irq mode to be decided on a per-port basis

Quoted 1 line · Rajat Jain

Today, there is a global pciehp_poll_mode module parameter using

which

Quoted 4 lines

either _all_ the hot-pluggable ports are to use polling, or _all_ the ports are to use interrupts.

In a system where a certain port has IRQ issues, today the only

option

Quoted 1 line

is to use the parameter that converts ALL the ports to use polling

mode.

Quoted 1 line

This is not good, and hence this patch intruduces a bit field that

can

Quoted 4 lines

be set using a PCI quirk that indicates that polling should always be used for this particular PCIe port. The remaining ports can still hoose to continue to operate in whatever mode they wish to.

Quoted 8 lines
Quoted 3 lines

I'm willing to merge this, but I'd prefer to merge it along with a quirk that actually sets dev->hotplug_polling. Otherwise it's dead code and I'll have no way to tell whether we need to keep it.

Bjorn,

Quoted 8 lines

what would be the proper location for such a quirk ? We use it to help simulating hotplug support on an IDT PES12NT3. The code is a bit more invasive than just the quirk itself, since it also needs to touch link and slot status registers, so quirks.c doesn't seem appropriate.

drivers/pci/pes12nt3.c, maybe, with a separate configuration option ? Or in the hotplug directory ?

If this is only for debug, i.e., you don't intend to ship a product using this simulated hotplug, maybe you should just keep both the quirk and this patch out of tree.

If you do want to eventually ship this code for some product, I think it'd be fine to put the quirk in drivers/pci/quirks.c, maybe with a config option to enable it. But without seeing the quirk, I can't really tell. A new file seems overkill unless it's something really huge -- I don't think we really have examples of dedicated files for other chip idiosyncrasies.

Bjorn

#5 lore raw
Quoted 62 lines

-----Original Message-----
From: Bjorn Helgaas [mailto:[email protected]]
Sent: Friday, April 25, 2014 9:44 AM
To: Guenter Roeck
Cc: Rajat Jain; [email protected]; linux- [email protected]; Rajat Jain
Subject: Re: [PATCH] pci/pciehp: Allow polling/irq mode to be decided on a per-port basis

Quoted 9 lines · Guenter Roeck
Quoted 9 lines

-----Original Message-----
From: Bjorn Helgaas [mailto:[email protected]]
Sent: Thursday, April 24, 2014 2:31 PM
To: Rajat Jain
Cc: [email protected]; [email protected]; Rajat Jain; Guenter Roeck
Subject: Re: [PATCH] pci/pciehp: Allow polling/irq mode to be

decided

Quoted 28 lines
Quoted 18 lines

on a per-port basis

Quoted 1 line · Rajat Jain

Today, there is a global pciehp_poll_mode module parameter using

which

Quoted 4 lines

either _all_ the hot-pluggable ports are to use polling, or _all_ the ports are to use interrupts.

In a system where a certain port has IRQ issues, today the only

option

Quoted 1 line

is to use the parameter that converts ALL the ports to use polling

mode.

Quoted 1 line

This is not good, and hence this patch intruduces a bit field that

can

Quoted 4 lines

be set using a PCI quirk that indicates that polling should always be used for this particular PCIe port. The remaining ports can still hoose to continue to operate in whatever mode they wish to.

Quoted 8 lines
Quoted 3 lines

I'm willing to merge this, but I'd prefer to merge it along with a quirk that actually sets dev->hotplug_polling. Otherwise it's dead code and I'll have no way to tell whether we need to keep it.

Bjorn,

Quoted 8 lines

what would be the proper location for such a quirk ?
We use it to help simulating hotplug support on an IDT PES12NT3. The code is a bit more invasive than just the quirk itself, since it also needs to touch link and slot status registers, so quirks.c doesn't seem appropriate.

drivers/pci/pes12nt3.c, maybe, with a separate configuration option ? Or in the hotplug directory ?

If this is only for debug, i.e., you don't intend to ship a product using this simulated hotplug, maybe you should just keep both the quirk and this patch out of tree.

Quoted 7 lines

If you do want to eventually ship this code for some product, I think it'd be fine to put the quirk in drivers/pci/quirks.c, maybe with a config option to enable it. But without seeing the quirk, I can't really tell. A new file seems overkill unless it's something really huge -- I don't think we really have examples of dedicated files for other chip idiosyncrasies.

I'd give it a 50:50 probability that it will ship. Current plan is that it is for development only, but I suspect that may change at some point.

I agree, this is kind of an outlier. If we push it upstream, it might mostly serve as a reference for others who might have similar problems - not just for the quirk itself, but as an example on how to intercept and manipulate pci configuration register accesses.

I attached the file so you can have a look.

Guenter

/*
* PTX5000 SIB - PCI fixup code
*
* Rajat Jain <[email protected]>
* Copyright 2014 Juniper Networks
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License v2 as published by the
* Free Software Foundation
*/

#include <linux/list.h>
#include <linux/pci.h>
#include <linux/jnx/pci_ids.h>
#include <linux/spinlock.h>

#define IDT_PES12NT3_DLSTS              0x268
#define IDT_PES12NT3_DLSTS_DLFSM        0x7
#define IDT_PES12NT3_DLSTS_LINKACTIVE   0x4
struct pes12nt3_pci_data {
	struct list_head list;
	struct device *dev;
	struct pci_ops ops;
	struct pci_ops *old_ops;
	bool lnksta_dllla;
	bool sltsta_dllsc;
};

static LIST_HEAD(pes12nt3_list);
static DEFINE_SPINLOCK(pes12nt3_lock);

static int pes12nt3_update_linkstatus(struct pes12nt3_pci_data *data,
struct pci_bus *bus, unsigned int devfn)

{
	u32 linkactive;
	bool dllla;
	int retval;

	retval = data->old_ops->read(bus, devfn, IDT_PES12NT3_DLSTS,
				     4, &linkactive);
	if (retval)
		return retval;
	if (linkactive == 0xffffffff)
		dllla = false;
	else
		dllla = (linkactive & IDT_PES12NT3_DLSTS_DLFSM) ==
						IDT_PES12NT3_DLSTS_LINKACTIVE;
	if (dllla != data->lnksta_dllla)
		data->sltsta_dllsc = true;
	data->lnksta_dllla = dllla;
	return 0;
}

static int pes12nt3_pci_read(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 *val)

{
	struct pes12nt3_pci_data *data =
			container_of(bus->ops, struct pes12nt3_pci_data, ops);
	int retval, pos;

	retval = data->old_ops->read(bus, devfn, where, size, val);

	/* Only need to change the registers at port leading to TF chip */
	if (retval || devfn != 0)
		return retval;

	retval = pes12nt3_update_linkstatus(data, bus, devfn);
	if (retval)
		return retval;

	pos = where - 0x40;

	/*
	 * PCI registers smaller than 32 bits may be read using
	 * different lengths at diferent offsets. Consider:
	 *
	 * +-----------------------------------+
	 * |  Reg-1 |  Reg-2 |  Reg-3 |  Reg-4 |
	 * +-----------------------------------+
	 * ^        ^        ^        ^
	 * ptr      ptr+1    ptr+2    ptr+3
	 *
	 * Reg-4 may be read by using a:
	 * 1 byte read at (ptr+3)
	 * 2 byte read at (ptr+2)
	 * 4 byte read at (ptr)
	 * etc
	 *
	 * We need to take of this here.
	 */

	switch (size) {
	case 4:
		switch (pos) {
		case 0:
			if (*val == 0xffffffff)
				*val = 0;
			else
				*val |= (PCI_EXP_FLAGS_SLOT << 16);
			break;
		case PCI_EXP_LNKCAP:
			if (*val == 0xffffffff)
				*val = 0;
			else
				*val |= PCI_EXP_LNKCAP_DLLLARC;
			break;
		case PCI_EXP_SLTCAP:
			if (*val == 0xffffffff) {
				*val = 0;
			} else {
				*val |= PCI_EXP_SLTCAP_HPC;
				*val = (*val & ~PCI_EXP_SLTCAP_PSN) |
					(bus->number << 19);
			}
			break;
		case PCI_EXP_LNKCTL:
			if (*val == 0xffffffff) {
				*val = 0;
			} else {
				if (data->lnksta_dllla)
					*val |= PCI_EXP_LNKSTA_DLLLA << 16;
				else
					*val &= ~(PCI_EXP_LNKSTA_DLLLA << 16);
			}
			break;
		}
		break;

	case 2:
		switch (pos) {
		case PCI_EXP_FLAGS_SLOT:
			if (*val == 0xffff)
				*val = 0;
			else
				*val |= PCI_EXP_FLAGS_SLOT;
			break;
		case PCI_EXP_LNKSTA:
			if (*val == 0xffff) {
				*val = 0;
			} else {
				if (data->lnksta_dllla)
					*val |= PCI_EXP_LNKSTA_DLLLA;
				else
					*val &= ~PCI_EXP_LNKSTA_DLLLA;
			}
			break;
		case PCI_EXP_SLTSTA:
			if (*val == 0xffff)
				*val = PCI_EXP_SLTSTA_DLLSC;
			else if (data->sltsta_dllsc)
				*val |= PCI_EXP_SLTSTA_DLLSC;
			break;
		}
		break;
	}
	return 0;
}

static int pes12nt3_pci_write(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 val)

{
	struct pes12nt3_pci_data *data =
			container_of(bus->ops, struct pes12nt3_pci_data, ops);
	int pos = where - 0x40;
	int retval;

	if (devfn == 0 && size == 2) {
		switch (pos) {
		case PCI_EXP_SLTSTA:
			if (val & PCI_EXP_SLTSTA_DLLSC)
				data->sltsta_dllsc = false;
			break;
		}
	}

	retval = data->old_ops->write(bus, devfn, where, size, val);
	if (retval || devfn != 0)
		return retval;

	/* Catch situations where the link status changed after being handled */
	return pes12nt3_update_linkstatus(data, bus, devfn);
}

static void pes12nt3_fake_linkstate_hotplug(struct pci_dev *dev)

{
	struct pes12nt3_pci_data *data;

	if (pci_pcie_type(dev) == PCI_EXP_TYPE_UPSTREAM) {
		data = kzalloc(sizeof(*data), GFP_KERNEL);
		if (data == NULL)
			return;
		data->ops.read = pes12nt3_pci_read;
		data->ops.write = pes12nt3_pci_write;
		data->old_ops = pci_bus_set_ops(dev->subordinate, &data->ops);
		data->dev = &dev->dev;
		spin_lock(&pes12nt3_lock);
		list_add(&data->list, &pes12nt3_list);
		spin_unlock(&pes12nt3_lock);
	} else if (pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM) {
		dev->hotplug_polling = 1; /* No IRQ support */
		dev->pcie_flags_reg |= PCI_EXP_FLAGS_SLOT;
	}
}
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_IDT, PCI_DEVICE_ID_IDT_PES12NT3_TRANS_AB,
			pes12nt3_fake_linkstate_hotplug);

static void pes12nt3_cleanup_entry(struct device *dev)

{
	struct pes12nt3_pci_data *data, *tmp;

	spin_lock(&pes12nt3_lock);
	list_for_each_entry_safe(data, tmp, &pes12nt3_list, list) {
		if (data->dev == dev) {
			list_del(&data->list);
			kfree(data);
			break;
		}
	}
	spin_unlock(&pes12nt3_lock);
}

static int pes12nt3_notifier_call(struct notifier_block *n,
unsigned long action, void *dev)

{
	if (action == BUS_NOTIFY_DEL_DEVICE)
		pes12nt3_cleanup_entry(dev);

	return NOTIFY_DONE;
}

static struct notifier_block pes12nt3_nb = {
.notifier_call = pes12nt3_notifier_call,
};

static int __init pes12nt3_init(void)

{
	bus_register_notifier(&pci_bus_type, &pes12nt3_nb);
	return 0;
}

subsys_initcall(pes12nt3_init);