diff mbox series

PCI: limit pci bridge and subsystem speed to 2.5GT/s

Message ID 20240805092010.82986-1-412574090@163.com (mailing list archive)
State Not Applicable
Headers show
Series PCI: limit pci bridge and subsystem speed to 2.5GT/s | expand

Commit Message

412574090@163.com Aug. 5, 2024, 9:20 a.m. UTC
From: weiyufeng <weiyufeng@kylinos.cn>

Add a kernel command-line option 'speed2_5g' to limit pci bridge
and subsystem devices speed to 2.5GT/s. As a debug method, this
provide for developer to temporarily slow down PCIe devices for
verification.

Signed-off-by: weiyufeng <weiyufeng@kylinos.cn>
---
 .../admin-guide/kernel-parameters.txt         |  8 ++++
 drivers/pci/pci.c                             |  6 ++-
 drivers/pci/pci.h                             |  1 +
 drivers/pci/probe.c                           | 41 +++++++++++++++++++
 include/linux/pci.h                           |  3 ++
 5 files changed, 58 insertions(+), 1 deletion(-)

Comments

Matthew Wilcox Aug. 5, 2024, 3:15 p.m. UTC | #1
On Mon, Aug 05, 2024 at 05:20:10PM +0800, 412574090@163.com wrote:
> From: weiyufeng <weiyufeng@kylinos.cn>
> 
> Add a kernel command-line option 'speed2_5g' to limit pci bridge
> and subsystem devices speed to 2.5GT/s. As a debug method, this
> provide for developer to temporarily slow down PCIe devices for
> verification.

I am opposed to this patch benig merged.  There's no harm to keep it for
your own testing.
diff mbox series

Patch

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index e86a098e06a8..e9b62e259128 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -4632,6 +4632,14 @@ 
 		nomio		[S390] Do not use MIO instructions.
 		norid		[S390] ignore the RID field and force use of
 				one PCI domain per PCI function
+		speed2_5g=
+				Format:
+				[<domain>:]<bus>:<dev>.<func>[/<dev>.<func>]*
+				pci:<vendor>:<device>[:<subvendor>:<subdevice>]
+				Specify one or more PCI bridge devices (in the
+				format specified above) separated by semicolons.
+				Each bridge device specified will limit the bridge
+				and subsystem devices speed to 2.5GT/s.
 
 	pcie_aspm=	[PCIE] Forcibly enable or ignore PCIe Active State Power
 			Management.
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index ffaaca0978cb..0bda7321167c 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -159,6 +159,7 @@  static bool pcie_ats_disabled;
 
 /* If set, the PCI config space of each device is printed during boot. */
 bool pci_early_dump;
+const char *pci_speed_2_5g;
 
 bool pci_ats_disabled(void)
 {
@@ -374,7 +375,7 @@  static int pci_dev_str_match_path(struct pci_dev *dev, const char *path,
  * Returns 1 if the string matches the device, 0 if it does not and
  * a negative error code if the string cannot be parsed.
  */
-static int pci_dev_str_match(struct pci_dev *dev, const char *p,
+int pci_dev_str_match(struct pci_dev *dev, const char *p,
 			     const char **endptr)
 {
 	int ret;
@@ -423,6 +424,7 @@  static int pci_dev_str_match(struct pci_dev *dev, const char *p,
 	*endptr = p;
 	return 1;
 }
+EXPORT_SYMBOL_GPL(pci_dev_str_match);
 
 static u8 __pci_find_next_cap_ttl(struct pci_bus *bus, unsigned int devfn,
 				  u8 pos, int cap, int *ttl)
@@ -6905,6 +6907,8 @@  static int __init pci_setup(char *str)
 				disable_acs_redir_param = str + 18;
 			} else if (!strncmp(str, "config_acs=", 11)) {
 				config_acs_param = str + 11;
+			} else if (!strncmp(str, "speed2_5g=", 10)) {
+				pci_speed_2_5g = str + 10;
 			} else {
 				pr_err("PCI: Unknown option `%s'\n", str);
 			}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 2fe6055a334d..615fa054e6b1 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -67,6 +67,7 @@ 
 
 extern const unsigned char pcie_link_speed[];
 extern bool pci_early_dump;
+extern const char *pci_speed_2_5g;
 
 bool pcie_cap_has_lnkctl(const struct pci_dev *dev);
 bool pcie_cap_has_lnkctl2(const struct pci_dev *dev);
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b14b9876c030..d64f328987a0 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -1846,6 +1846,45 @@  static void early_dump_pci_device(struct pci_dev *pdev)
 		       value, 256, false);
 }
 
+static void pcie_retrain_downstream_2_5g(struct pci_dev *pdev)
+{
+	u16 lnkctl2;
+	const char *p;
+	int ret;
+
+	p = pci_speed_2_5g;
+	while (*p) {
+		ret = pci_dev_str_match(pdev, p, &p);
+		if (ret < 0) {
+			pr_info_once("PCI: Can't parse pci_speed_2_5g parameter: %s\n",
+				pci_speed_2_5g);
+			break;
+		} else if (ret == 1) {
+			/* Found a match */
+			break;
+		}
+
+		if (*p != ';' && *p != ',') {
+			/* End of param or invalid format */
+			break;
+		}
+		p++;
+	}
+
+	if (ret != 1)
+		return;
+
+	pci_info(pdev, "set downstream link at 2.5GT/s\n");
+	pcie_capability_read_word(pdev, PCI_EXP_LNKCTL2, &lnkctl2);
+	lnkctl2 &= ~PCI_EXP_LNKCTL2_TLS;
+	lnkctl2 |= PCI_EXP_LNKCTL2_TLS_2_5GT;
+	pcie_capability_write_word(pdev, PCI_EXP_LNKCTL2, lnkctl2);
+
+	if (pcie_retrain_link(pdev, true)) {
+		pci_info(pdev, "retraining failed\n");
+	}
+}
+
 static const char *pci_type_str(struct pci_dev *dev)
 {
 	static const char * const str[] = {
@@ -2041,6 +2080,8 @@  int pci_setup_device(struct pci_dev *dev)
 			pci_read_config_word(dev, pos + PCI_SSVID_VENDOR_ID, &dev->subsystem_vendor);
 			pci_read_config_word(dev, pos + PCI_SSVID_DEVICE_ID, &dev->subsystem_device);
 		}
+		if (pci_speed_2_5g)
+			pcie_retrain_downstream_2_5g(dev);
 		break;
 
 	case PCI_HEADER_TYPE_CARDBUS:		    /* CardBus bridge header */
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 4246cb790c7b..18198af09142 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -1184,6 +1184,7 @@  void pci_sort_breadthfirst(void);
 
 u8 pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap);
 u8 pci_find_capability(struct pci_dev *dev, int cap);
+int pci_dev_str_match(struct pci_dev *dev, const char *p, const char **endptr);
 u8 pci_find_next_capability(struct pci_dev *dev, u8 pos, int cap);
 u8 pci_find_ht_capability(struct pci_dev *dev, int ht_cap);
 u8 pci_find_next_ht_capability(struct pci_dev *dev, u8 pos, int ht_cap);
@@ -1984,6 +1985,8 @@  static inline int pci_register_driver(struct pci_driver *drv)
 static inline void pci_unregister_driver(struct pci_driver *drv) { }
 static inline u8 pci_find_capability(struct pci_dev *dev, int cap)
 { return 0; }
+static inline int pci_dev_str_match(struct pci_dev *dev, const char *p, const char **endptr)
+{ return 0; }
 static inline u8 pci_find_next_capability(struct pci_dev *dev, u8 post, int cap)
 { return 0; }
 static inline u16 pci_find_ext_capability(struct pci_dev *dev, int cap)