diff mbox

[2/3] PCI: add OBFF enable/disable support

Message ID 1304624030-1922-3-git-send-email-jbarnes@virtuousgeek.org (mailing list archive)
State Superseded, archived
Headers show

Commit Message

Jesse Barnes May 5, 2011, 7:33 p.m. UTC
OBFF (optimized buffer flush/fill), where supported, can help improve
energy efficiency by giving devices information about when interrupts
and other activity will have a reduced power impact.  It requires
support from both the device and system (i.e. not only does the device
need to respond to OBFF messages, but the platform must be capable of
generating and routing them to the end point).

Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
---
 drivers/pci/pci.c        |   89 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/pci.h      |   16 ++++++++
 include/linux/pci_regs.h |    6 +++
 3 files changed, 111 insertions(+), 0 deletions(-)

Comments

Matthew Wilcox May 5, 2011, 8:15 p.m. UTC | #1
On Thu, May 05, 2011 at 12:33:49PM -0700, Jesse Barnes wrote:
> OBFF (optimized buffer flush/fill), where supported, can help improve
> energy efficiency by giving devices information about when interrupts
> and other activity will have a reduced power impact.  It requires
> support from both the device and system (i.e. not only does the device
> need to respond to OBFF messages, but the platform must be capable of
> generating and routing them to the end point).

I'm really confused by the OBFF spec.  There's all kinds of caveats on
enabling OBFF; for example, it seems that the WAKE# signal might be shared
between all devices below a given switch ... but how are we to know?
Figure 6-19 in the 10Nov10 spec shows a case where _all_ the devices
connected to Switch B would need to support OBFF in order to enable it.
And not necessarily directly connected either; the device below Switch
A shares WAKE# with the devices directly below the root port.

I think we should probably leave it up to the BIOS to configure OBFF.
I don't think we can do it.

That said, I did notice a bug ...

> +	/* Make sure the topology supports OBFF as well */
> +	if (dev->bus) {
> +		ret = pci_enable_obff(dev->bus->self, type);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	pci_read_config_word(dev, pos + PCI_EXP_DEVCTL2, &ctrl);
> +	if (cap & PCI_EXP_OBFF_WAKE)
> +		ctrl |= PCI_EXP_OBFF_WAKE_EN;
> +	else {
> +		switch (type) {
> +		case PCI_EXP_OBFF_SIGNAL_L0:
> +			ctrl |= PCI_EXP_OBFF_MSGA_EN;
> +			break;
> +		case PCI_EXP_OBFF_SIGNAL_ALWAYS:
> +			ctrl |= PCI_EXP_OBFF_MSGB_EN;
> +			break;
> +		default:
> +			WARN(1, "bad OBFF signal type\n");
> +			return -ENOTSUPP;
> +		}
> +	}

If Device 1 asks for L0 and Device 2 asks for Always, any component above
them in the hierarchy gets WAKE set, which isn't going to work out very well.
It should look like this:

		switch (type) {
		case PCI_EXP_OBFF_SIGNAL_L0:
			if (!(ctrl & PCI_EXP_OBFF_WAKE_EN))
				ctrl |= PCI_EXP_OBFF_MSGA_EN;
			break;
		case PCI_EXP_OBFF_SIGNAL_ALWAYS:
			ctrl &= ~PCI_EXP_OBFF_WAKE_EN;
			ctrl |= PCI_EXP_OBFF_MSGB_EN;
			break;

> +	pci_read_config_word(dev, pos + PCI_EXP_DEVCTL2, &ctrl);
> +	ctrl &= ~(PCI_EXP_OBFF_MSGA_EN | PCI_EXP_OBFF_MSGA_EN |
> +		  PCI_EXP_OBFF_WAKE_EN);

That simplifies to ctrl &= ~PCI_EXP_OBFF_WAKE_EN;
diff mbox

Patch

diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 44bf8f0..44c1d3d 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -1888,6 +1888,95 @@  void pci_disable_ido(struct pci_dev *dev, unsigned long type)
 	pci_write_config_word(dev, pos + PCI_EXP_DEVCTL2, ctrl);
 }
 
+/**
+ * pci_enable_obff - enable optimized buffer flush/fill
+ * @dev: PCI device
+ * @type: type of signaling to use
+ *
+ * Try to enable @type OBFF signaling on @dev.  It will try using WAKE#
+ * signaling if possible, falling back to message signaling only if
+ * WAKE# isn't supported.  @type should indicate whether the PCIe link
+ * be brought out of L0s or L1 to send the message.  It should be either
+ * %PCI_EXP_OBFF_SIGNAL_ALWAYS or %PCI_OBFF_SIGNAL_L0.
+ *
+ * If your device can benefit from receiving all messages, even at the
+ * power cost of bringing the link back up from a low power state, use
+ * %PCI_EXP_OBFF_SIGNAL_ALWAYS.  Otherwise, use %PCI_OBFF_SIGNAL_L0 (the
+ * preferred type).
+ *
+ * RETURNS:
+ * Zero on success, appropriate error number on failure.
+ */
+int pci_enable_obff(struct pci_dev *dev, enum pci_obff_signal_type type)
+{
+	int pos;
+	u32 cap;
+	u16 ctrl;
+	int ret;
+
+	if (!pci_is_pcie(dev))
+		return -ENOTSUPP;
+
+	pos = pci_pcie_cap(dev);
+	if (!pos)
+		return -ENOTSUPP;
+
+	pci_read_config_dword(dev, pos + PCI_EXP_DEVCAP2, &cap);
+	if (!(cap & PCI_EXP_OBFF_MASK))
+		return -ENOTSUPP; /* no OBFF support at all */
+
+	/* Make sure the topology supports OBFF as well */
+	if (dev->bus) {
+		ret = pci_enable_obff(dev->bus->self, type);
+		if (ret)
+			return ret;
+	}
+
+	pci_read_config_word(dev, pos + PCI_EXP_DEVCTL2, &ctrl);
+	if (cap & PCI_EXP_OBFF_WAKE)
+		ctrl |= PCI_EXP_OBFF_WAKE_EN;
+	else {
+		switch (type) {
+		case PCI_EXP_OBFF_SIGNAL_L0:
+			ctrl |= PCI_EXP_OBFF_MSGA_EN;
+			break;
+		case PCI_EXP_OBFF_SIGNAL_ALWAYS:
+			ctrl |= PCI_EXP_OBFF_MSGB_EN;
+			break;
+		default:
+			WARN(1, "bad OBFF signal type\n");
+			return -ENOTSUPP;
+		}
+	}
+	pci_write_config_word(dev, pos + PCI_EXP_DEVCTL2, ctrl);
+
+	return 0;
+}
+
+/**
+ * pci_disable_obff - disable optimized buffer flush/fill
+ * @dev: PCI device
+ *
+ * Disable OBFF on @dev.
+ */
+void pci_disable_obff(struct pci_dev *dev)
+{
+	int pos;
+	u16 ctrl;
+
+	if (!pci_is_pcie(dev))
+		return;
+
+	pos = pci_pcie_cap(dev);
+	if (!pos)
+		return;
+
+	pci_read_config_word(dev, pos + PCI_EXP_DEVCTL2, &ctrl);
+	ctrl &= ~(PCI_EXP_OBFF_MSGA_EN | PCI_EXP_OBFF_MSGA_EN |
+		  PCI_EXP_OBFF_WAKE_EN);
+	pci_write_config_word(dev, pos + PCI_EXP_DEVCTL2, ctrl);
+}
+
 static int pci_acs_enable;
 
 /**
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 551ddcb..45a035c 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -833,6 +833,13 @@  static inline int pci_enable_wake(struct pci_dev *dev, pci_power_t state,
 void pci_enable_ido(struct pci_dev *dev, unsigned long type);
 void pci_disable_ido(struct pci_dev *dev, unsigned long type);
 
+enum pci_obff_signal_type {
+	PCI_EXP_OBFF_SIGNAL_L0,
+	PCI_EXP_OBFF_SIGNAL_ALWAYS,
+};
+int pci_enable_obff(struct pci_dev *dev, enum pci_obff_signal_type);
+void pci_disable_obff(struct pci_dev *dev);
+
 /* For use by arch with custom probe code */
 void set_pcie_port_type(struct pci_dev *pdev);
 void set_pcie_hotplug_bridge(struct pci_dev *pdev);
@@ -1220,6 +1227,15 @@  static inline void pci_disable_ido(struct pci_dev *dev, unsigned long type)
 {
 }
 
+static inline int pci_enable_obff(struct pci_dev *dev, unsigned long type)
+{
+	return 0;
+}
+
+static inline void pci_disable_obff(struct pci_dev *dev)
+{
+}
+
 static inline int pci_request_regions(struct pci_dev *dev, const char *res_name)
 {
 	return -EIO;
diff --git a/include/linux/pci_regs.h b/include/linux/pci_regs.h
index d9acf9b..aa42026 100644
--- a/include/linux/pci_regs.h
+++ b/include/linux/pci_regs.h
@@ -508,10 +508,16 @@ 
 #define PCI_EXP_RTSTA_PENDING	0x20000 /* PME pending */
 #define PCI_EXP_DEVCAP2		36	/* Device Capabilities 2 */
 #define  PCI_EXP_DEVCAP2_ARI	0x20	/* Alternative Routing-ID */
+#define  PCI_EXP_OBFF_MASK	0xc0000 /* OBFF support mechanism */
+#define  PCI_EXP_OBFF_MSG	0x40000 /* New message signaling */
+#define  PCI_EXP_OBFF_WAKE	0x80000 /* Re-use WAKE# for OBFF */
 #define PCI_EXP_DEVCTL2		40	/* Device Control 2 */
 #define  PCI_EXP_DEVCTL2_ARI	0x20	/* Alternative Routing-ID */
 #define  PCI_EXP_IDO_REQ_EN	0x100	/* ID-based ordering request enable */
 #define  PCI_EXP_IDO_CMP_EN	0x200	/* ID-based ordering completion enable */
+#define  PCI_EXP_OBFF_MSGA_EN	0x2000	/* OBFF enable with Message type A */
+#define  PCI_EXP_OBFF_MSGB_EN	0x4000	/* OBFF enable with Message type B */
+#define  PCI_EXP_OBFF_WAKE_EN	0x6000	/* OBFF using WAKE# signaling */
 #define PCI_EXP_LNKCTL2		48	/* Link Control 2 */
 #define PCI_EXP_SLTCTL2		56	/* Slot Control 2 */