diff mbox

[3/3] PCI: add latency tolerance reporting enable/disable support

Message ID 1304624030-1922-4-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
Latency tolerance reporting allows devices to send messages to the root
complex indicating their latency tolerance for snooped & unsnooped
memory transactions.  Add support for enabling & disabling this
feature, along with a routine to set the max latencies a device should
send upstream.

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

Comments

Matthew Wilcox May 5, 2011, 7:51 p.m. UTC | #1
On Thu, May 05, 2011 at 12:33:50PM -0700, Jesse Barnes wrote:
> +bool pci_ltr_supported(struct pci_dev *dev)
> +{
> +	int pos;
> +	u32 cap;
> +
> +	if (!pci_is_pcie(dev))
> +		return false;
> +
> +	pos = pci_pcie_cap(dev);
> +	if (!pos)
> +		return false;
> +
> +	pci_read_config_dword(dev, pos + PCI_EXP_DEVCAP2, &cap);
> +
> +	return cap & PCI_EXP_DEVCAP2_LTR;
> +}

Missing EXPORT_SYMBOL?  (throughout)

> +/**
> + * pci_set_ltr - set LTR latency values
> + * @dev: PCI device
> + * @latencies: LTR latency values & scaling
> + *
> + * Set the LTR cap registers to the values provided by @latencies.
> + */
> +int pci_set_ltr(struct pci_dev *dev, struct pci_ltr_latencies *latencies)
> +{

I don't like this API.  Why not just pass a u32 snoop_lat and nosnoop_lat
(in ns) and do:

	int scale = 0;
	while (latency > 1023) {
		latency = (latency + 31) / 32;
		scale++;
	}

(a u32 latency gives you up to 4 seconds of latency, which is surely
more than any device can tolerate ... and doesn't let you max out scale)

> +	int pos, ret;
> +	u32 val;
> +
> +	if (!pci_ltr_supported(dev))
> +		return -ENOTSUPP;
> +
> +	if (latencies->max_snoop_value > PCI_LTR_VALUE_MASK ||
> +	    latencies->max_nosnoop_value > PCI_LTR_VALUE_MASK)
> +		return -EINVAL;
> +
> +	if ((latencies->max_snoop_scale >
> +	     (PCI_LTR_SCALE_MASK >> PCI_LTR_SCALE_SHIFT)) ||
> +	    (latencies->max_nosnoop_scale >
> +	     (PCI_LTR_SCALE_MASK >> PCI_LTR_SCALE_SHIFT)))
> +		return -EINVAL;
> +
> +	pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_LTR);
> +	if (!pos)
> +		return -ENOTSUPP;
> +
> +	val = (latencies->max_snoop_scale << PCI_LTR_SCALE_SHIFT) |
> +		latencies->max_snoop_value;
> +	ret = pci_write_config_dword(dev, pos + PCI_LTR_MAX_SNOOP_LAT, val);
> +	if (ret != 4)
> +		return -EIO;

Spec indicates these are word values ... can probably hit both registers
with a single dword write though.

> +	val = (latencies->max_nosnoop_scale << PCI_LTR_SCALE_SHIFT) |
> +		latencies->max_nosnoop_value;
> +	ret = pci_write_config_dword(dev, pos + PCI_LTR_MAX_NOSNOOP_LAT, val);
> +	if (ret != 4)
> +		return -EIO;
> +
> +	return 0;
> +}
> +
>  static int pci_acs_enable;
>  
>  /**
> diff --git a/include/linux/pci.h b/include/linux/pci.h
> index 45a035c..c5b2d9c 100644
> --- a/include/linux/pci.h
> +++ b/include/linux/pci.h
> @@ -840,6 +840,17 @@ enum pci_obff_signal_type {
>  int pci_enable_obff(struct pci_dev *dev, enum pci_obff_signal_type);
>  void pci_disable_obff(struct pci_dev *dev);
>  
> +bool pci_ltr_supported(struct pci_dev *dev);
> +int pci_enable_ltr(struct pci_dev *dev);
> +void pci_disable_ltr(struct pci_dev *dev);
> +struct pci_ltr_latencies {
> +	u16 max_snoop_value;
> +	u8 max_snoop_scale;
> +	u16 max_nosnoop_value;
> +	u8 max_nosnoop_scale;
> +};
> +int pci_set_ltr(struct pci_dev *dev, struct pci_ltr_latencies *latencies);
> +
>  /* 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);
> diff --git a/include/linux/pci_regs.h b/include/linux/pci_regs.h
> index aa42026..e884096 100644
> --- a/include/linux/pci_regs.h
> +++ b/include/linux/pci_regs.h
> @@ -508,6 +508,7 @@
>  #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_DEVCAP2_LTR	0x800	/* Latency tolerance reporting */
>  #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 */
> @@ -515,6 +516,7 @@
>  #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_LTR_EN		0x400	/* Latency tolerance reporting */
>  #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 */
> @@ -535,6 +537,7 @@
>  #define PCI_EXT_CAP_ID_ARI	14
>  #define PCI_EXT_CAP_ID_ATS	15
>  #define PCI_EXT_CAP_ID_SRIOV	16
> +#define PCI_EXT_CAP_ID_LTR	24
>  
>  /* Advanced Error Reporting */
>  #define PCI_ERR_UNCOR_STATUS	4	/* Uncorrectable Error Status */
> @@ -691,6 +694,12 @@
>  #define  PCI_SRIOV_VFM_MO	0x2	/* Active.MigrateOut */
>  #define  PCI_SRIOV_VFM_AV	0x3	/* Active.Available */
>  
> +#define PCI_LTR_MAX_SNOOP_LAT	0x4
> +#define PCI_LTR_MAX_NOSNOOP_LAT	0x6
> +#define  PCI_LTR_VALUE_MASK	0x000003ff
> +#define  PCI_LTR_SCALE_MASK	0x00001c00
> +#define  PCI_LTR_SCALE_SHIFT	10
> +
>  /* Access Control Service */
>  #define PCI_ACS_CAP		0x04	/* ACS Capability Register */
>  #define  PCI_ACS_SV		0x01	/* Source Validation */
> -- 
> 1.7.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-pci" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jesse Barnes May 5, 2011, 7:54 p.m. UTC | #2
On Thu, 5 May 2011 13:51:58 -0600
Matthew Wilcox <matthew@wil.cx> wrote:

> On Thu, May 05, 2011 at 12:33:50PM -0700, Jesse Barnes wrote:
> > +bool pci_ltr_supported(struct pci_dev *dev)
> > +{
> > +	int pos;
> > +	u32 cap;
> > +
> > +	if (!pci_is_pcie(dev))
> > +		return false;
> > +
> > +	pos = pci_pcie_cap(dev);
> > +	if (!pos)
> > +		return false;
> > +
> > +	pci_read_config_dword(dev, pos + PCI_EXP_DEVCAP2, &cap);
> > +
> > +	return cap & PCI_EXP_DEVCAP2_LTR;
> > +}
> 
> Missing EXPORT_SYMBOL?  (throughout)

Oops, will fix.  I guess drivers would have run into that pretty
quickly. :)

> 
> > +/**
> > + * pci_set_ltr - set LTR latency values
> > + * @dev: PCI device
> > + * @latencies: LTR latency values & scaling
> > + *
> > + * Set the LTR cap registers to the values provided by @latencies.
> > + */
> > +int pci_set_ltr(struct pci_dev *dev, struct pci_ltr_latencies *latencies)
> > +{
> 
> I don't like this API.  Why not just pass a u32 snoop_lat and nosnoop_lat
> (in ns) and do:
> 
> 	int scale = 0;
> 	while (latency > 1023) {
> 		latency = (latency + 31) / 32;
> 		scale++;
> 	}
> 
> (a u32 latency gives you up to 4 seconds of latency, which is surely
> more than any device can tolerate ... and doesn't let you max out scale)

Agreed, not sure why I thought this was a good API when I coded it up.
Taking simple ns is much easier from a caller perspective.

> > +	int pos, ret;
> > +	u32 val;
> > +
> > +	if (!pci_ltr_supported(dev))
> > +		return -ENOTSUPP;
> > +
> > +	if (latencies->max_snoop_value > PCI_LTR_VALUE_MASK ||
> > +	    latencies->max_nosnoop_value > PCI_LTR_VALUE_MASK)
> > +		return -EINVAL;
> > +
> > +	if ((latencies->max_snoop_scale >
> > +	     (PCI_LTR_SCALE_MASK >> PCI_LTR_SCALE_SHIFT)) ||
> > +	    (latencies->max_nosnoop_scale >
> > +	     (PCI_LTR_SCALE_MASK >> PCI_LTR_SCALE_SHIFT)))
> > +		return -EINVAL;
> > +
> > +	pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_LTR);
> > +	if (!pos)
> > +		return -ENOTSUPP;
> > +
> > +	val = (latencies->max_snoop_scale << PCI_LTR_SCALE_SHIFT) |
> > +		latencies->max_snoop_value;
> > +	ret = pci_write_config_dword(dev, pos + PCI_LTR_MAX_SNOOP_LAT, val);
> > +	if (ret != 4)
> > +		return -EIO;
> 
> Spec indicates these are word values ... can probably hit both registers
> with a single dword write though.

May as well split the writes up just to avoid confusion.

Thanks a lot for checking it out.
diff mbox

Patch

diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 44c1d3d..56f073e 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -1977,6 +1977,140 @@  void pci_disable_obff(struct pci_dev *dev)
 	pci_write_config_word(dev, pos + PCI_EXP_DEVCTL2, ctrl);
 }
 
+/**
+ * pci_ltr_supported - check whether a device supports LTR
+ * @dev: PCI device
+ *
+ * RETURNS:
+ * True if @dev supports latency tolerance reporting, false otherwise.
+ */
+bool pci_ltr_supported(struct pci_dev *dev)
+{
+	int pos;
+	u32 cap;
+
+	if (!pci_is_pcie(dev))
+		return false;
+
+	pos = pci_pcie_cap(dev);
+	if (!pos)
+		return false;
+
+	pci_read_config_dword(dev, pos + PCI_EXP_DEVCAP2, &cap);
+
+	return cap & PCI_EXP_DEVCAP2_LTR;
+}
+
+/**
+ * pci_enable_ltr - enable latency tolerance reporting
+ * @dev: PCI device
+ *
+ * Enable LTR on @dev if possible, which means enabling it first on
+ * upstream ports.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int pci_enable_ltr(struct pci_dev *dev)
+{
+	int pos;
+	u16 ctrl;
+	int ret;
+
+	if (!pci_ltr_supported(dev))
+		return -ENOTSUPP;
+
+	pos = pci_pcie_cap(dev);
+	if (!pos)
+		return -ENOTSUPP;
+
+	/* Only primary function can enable/disable LTR */
+	if (PCI_FUNC(dev->devfn) != 0)
+		return -EINVAL;
+
+	/* Enable upstream ports first */
+	if (dev->bus) {
+		ret = pci_enable_ltr(dev->bus->self);
+		if (ret)
+			return ret;
+	}
+
+	pci_read_config_word(dev, pos + PCI_EXP_DEVCTL2, &ctrl);
+	ctrl |= PCI_EXP_LTR_EN;
+	pci_write_config_word(dev, pos + PCI_EXP_DEVCTL2, ctrl);
+
+	return 0;
+}
+
+/**
+ * pci_disable_ltr - disable latency tolerance reporting
+ * @dev: PCI device
+ */
+void pci_disable_ltr(struct pci_dev *dev)
+{
+	int pos;
+	u16 ctrl;
+
+	if (!pci_ltr_supported(dev))
+		return;
+
+	pos = pci_pcie_cap(dev);
+	if (!pos)
+		return;
+
+	/* Only primary function can enable/disable LTR */
+	if (PCI_FUNC(dev->devfn) != 0)
+		return;
+
+	pci_read_config_word(dev, pos + PCI_EXP_DEVCTL2, &ctrl);
+	ctrl &= ~PCI_EXP_LTR_EN;
+	pci_write_config_word(dev, pos + PCI_EXP_DEVCTL2, ctrl);
+}
+
+/**
+ * pci_set_ltr - set LTR latency values
+ * @dev: PCI device
+ * @latencies: LTR latency values & scaling
+ *
+ * Set the LTR cap registers to the values provided by @latencies.
+ */
+int pci_set_ltr(struct pci_dev *dev, struct pci_ltr_latencies *latencies)
+{
+	int pos, ret;
+	u32 val;
+
+	if (!pci_ltr_supported(dev))
+		return -ENOTSUPP;
+
+	if (latencies->max_snoop_value > PCI_LTR_VALUE_MASK ||
+	    latencies->max_nosnoop_value > PCI_LTR_VALUE_MASK)
+		return -EINVAL;
+
+	if ((latencies->max_snoop_scale >
+	     (PCI_LTR_SCALE_MASK >> PCI_LTR_SCALE_SHIFT)) ||
+	    (latencies->max_nosnoop_scale >
+	     (PCI_LTR_SCALE_MASK >> PCI_LTR_SCALE_SHIFT)))
+		return -EINVAL;
+
+	pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_LTR);
+	if (!pos)
+		return -ENOTSUPP;
+
+	val = (latencies->max_snoop_scale << PCI_LTR_SCALE_SHIFT) |
+		latencies->max_snoop_value;
+	ret = pci_write_config_dword(dev, pos + PCI_LTR_MAX_SNOOP_LAT, val);
+	if (ret != 4)
+		return -EIO;
+
+	val = (latencies->max_nosnoop_scale << PCI_LTR_SCALE_SHIFT) |
+		latencies->max_nosnoop_value;
+	ret = pci_write_config_dword(dev, pos + PCI_LTR_MAX_NOSNOOP_LAT, val);
+	if (ret != 4)
+		return -EIO;
+
+	return 0;
+}
+
 static int pci_acs_enable;
 
 /**
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 45a035c..c5b2d9c 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -840,6 +840,17 @@  enum pci_obff_signal_type {
 int pci_enable_obff(struct pci_dev *dev, enum pci_obff_signal_type);
 void pci_disable_obff(struct pci_dev *dev);
 
+bool pci_ltr_supported(struct pci_dev *dev);
+int pci_enable_ltr(struct pci_dev *dev);
+void pci_disable_ltr(struct pci_dev *dev);
+struct pci_ltr_latencies {
+	u16 max_snoop_value;
+	u8 max_snoop_scale;
+	u16 max_nosnoop_value;
+	u8 max_nosnoop_scale;
+};
+int pci_set_ltr(struct pci_dev *dev, struct pci_ltr_latencies *latencies);
+
 /* 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);
diff --git a/include/linux/pci_regs.h b/include/linux/pci_regs.h
index aa42026..e884096 100644
--- a/include/linux/pci_regs.h
+++ b/include/linux/pci_regs.h
@@ -508,6 +508,7 @@ 
 #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_DEVCAP2_LTR	0x800	/* Latency tolerance reporting */
 #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 */
@@ -515,6 +516,7 @@ 
 #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_LTR_EN		0x400	/* Latency tolerance reporting */
 #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 */
@@ -535,6 +537,7 @@ 
 #define PCI_EXT_CAP_ID_ARI	14
 #define PCI_EXT_CAP_ID_ATS	15
 #define PCI_EXT_CAP_ID_SRIOV	16
+#define PCI_EXT_CAP_ID_LTR	24
 
 /* Advanced Error Reporting */
 #define PCI_ERR_UNCOR_STATUS	4	/* Uncorrectable Error Status */
@@ -691,6 +694,12 @@ 
 #define  PCI_SRIOV_VFM_MO	0x2	/* Active.MigrateOut */
 #define  PCI_SRIOV_VFM_AV	0x3	/* Active.Available */
 
+#define PCI_LTR_MAX_SNOOP_LAT	0x4
+#define PCI_LTR_MAX_NOSNOOP_LAT	0x6
+#define  PCI_LTR_VALUE_MASK	0x000003ff
+#define  PCI_LTR_SCALE_MASK	0x00001c00
+#define  PCI_LTR_SCALE_SHIFT	10
+
 /* Access Control Service */
 #define PCI_ACS_CAP		0x04	/* ACS Capability Register */
 #define  PCI_ACS_SV		0x01	/* Source Validation */