[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <20240805092010.82986-1-412574090@163.com>
Date: Mon, 5 Aug 2024 17:20:10 +0800
From: 412574090@....com
To: corbet@....net,
bhelgaas@...gle.com
Cc: linux-doc@...r.kernel.org,
linux-kernel@...r.kernel.org,
linux-pci@...r.kernel.org,
xiongxin@...inos.cn,
weiyufeng <weiyufeng@...inos.cn>
Subject: [PATCH] PCI: limit pci bridge and subsystem speed to 2.5GT/s
From: weiyufeng <weiyufeng@...inos.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@...inos.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(-)
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)
--
2.25.1
Powered by blists - more mailing lists