]>
Commit | Line | Data |
---|---|---|
e8303bb7 AG |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * PCI Express Link Bandwidth Notification services driver | |
4 | * Author: Alexandru Gagniuc <mr.nuke.me@gmail.com> | |
5 | * | |
6 | * Copyright (C) 2019, Dell Inc | |
7 | * | |
8 | * The PCIe Link Bandwidth Notification provides a way to notify the | |
9 | * operating system when the link width or data rate changes. This | |
10 | * capability is required for all root ports and downstream ports | |
11 | * supporting links wider than x1 and/or multiple link speeds. | |
12 | * | |
13 | * This service port driver hooks into the bandwidth notification interrupt | |
14 | * and warns when links become degraded in operation. | |
15 | */ | |
16 | ||
17 | #include "../pci.h" | |
18 | #include "portdrv.h" | |
19 | ||
20 | static bool pcie_link_bandwidth_notification_supported(struct pci_dev *dev) | |
21 | { | |
22 | int ret; | |
23 | u32 lnk_cap; | |
24 | ||
25 | ret = pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &lnk_cap); | |
26 | return (ret == PCIBIOS_SUCCESSFUL) && (lnk_cap & PCI_EXP_LNKCAP_LBNC); | |
27 | } | |
28 | ||
29 | static void pcie_enable_link_bandwidth_notification(struct pci_dev *dev) | |
30 | { | |
31 | u16 lnk_ctl; | |
32 | ||
55397ce8 LW |
33 | pcie_capability_write_word(dev, PCI_EXP_LNKSTA, PCI_EXP_LNKSTA_LBMS); |
34 | ||
e8303bb7 AG |
35 | pcie_capability_read_word(dev, PCI_EXP_LNKCTL, &lnk_ctl); |
36 | lnk_ctl |= PCI_EXP_LNKCTL_LBMIE; | |
37 | pcie_capability_write_word(dev, PCI_EXP_LNKCTL, lnk_ctl); | |
38 | } | |
39 | ||
40 | static void pcie_disable_link_bandwidth_notification(struct pci_dev *dev) | |
41 | { | |
42 | u16 lnk_ctl; | |
43 | ||
44 | pcie_capability_read_word(dev, PCI_EXP_LNKCTL, &lnk_ctl); | |
45 | lnk_ctl &= ~PCI_EXP_LNKCTL_LBMIE; | |
46 | pcie_capability_write_word(dev, PCI_EXP_LNKCTL, lnk_ctl); | |
47 | } | |
48 | ||
3e82a7f9 | 49 | static irqreturn_t pcie_bw_notification_irq(int irq, void *context) |
e8303bb7 AG |
50 | { |
51 | struct pcie_device *srv = context; | |
52 | struct pci_dev *port = srv->port; | |
e8303bb7 AG |
53 | u16 link_status, events; |
54 | int ret; | |
55 | ||
56 | ret = pcie_capability_read_word(port, PCI_EXP_LNKSTA, &link_status); | |
57 | events = link_status & PCI_EXP_LNKSTA_LBMS; | |
58 | ||
59 | if (ret != PCIBIOS_SUCCESSFUL || !events) | |
60 | return IRQ_NONE; | |
61 | ||
3e82a7f9 AG |
62 | pcie_capability_write_word(port, PCI_EXP_LNKSTA, events); |
63 | pcie_update_link_speed(port->subordinate, link_status); | |
64 | return IRQ_WAKE_THREAD; | |
65 | } | |
66 | ||
67 | static irqreturn_t pcie_bw_notification_handler(int irq, void *context) | |
68 | { | |
69 | struct pcie_device *srv = context; | |
70 | struct pci_dev *port = srv->port; | |
71 | struct pci_dev *dev; | |
72 | ||
e8303bb7 AG |
73 | /* |
74 | * Print status from downstream devices, not this root port or | |
75 | * downstream switch port. | |
76 | */ | |
77 | down_read(&pci_bus_sem); | |
78 | list_for_each_entry(dev, &port->subordinate->devices, bus_list) | |
0fa635ae | 79 | pcie_report_downtraining(dev); |
e8303bb7 AG |
80 | up_read(&pci_bus_sem); |
81 | ||
e8303bb7 AG |
82 | return IRQ_HANDLED; |
83 | } | |
84 | ||
85 | static int pcie_bandwidth_notification_probe(struct pcie_device *srv) | |
86 | { | |
87 | int ret; | |
88 | ||
89 | /* Single-width or single-speed ports do not have to support this. */ | |
90 | if (!pcie_link_bandwidth_notification_supported(srv->port)) | |
91 | return -ENODEV; | |
92 | ||
3e82a7f9 AG |
93 | ret = request_threaded_irq(srv->irq, pcie_bw_notification_irq, |
94 | pcie_bw_notification_handler, | |
e8303bb7 AG |
95 | IRQF_SHARED, "PCIe BW notif", srv); |
96 | if (ret) | |
97 | return ret; | |
98 | ||
99 | pcie_enable_link_bandwidth_notification(srv->port); | |
100 | ||
101 | return 0; | |
102 | } | |
103 | ||
104 | static void pcie_bandwidth_notification_remove(struct pcie_device *srv) | |
105 | { | |
106 | pcie_disable_link_bandwidth_notification(srv->port); | |
107 | free_irq(srv->irq, srv); | |
108 | } | |
109 | ||
6056bed9 MW |
110 | static int pcie_bandwidth_notification_suspend(struct pcie_device *srv) |
111 | { | |
112 | pcie_disable_link_bandwidth_notification(srv->port); | |
113 | return 0; | |
114 | } | |
115 | ||
116 | static int pcie_bandwidth_notification_resume(struct pcie_device *srv) | |
117 | { | |
118 | pcie_enable_link_bandwidth_notification(srv->port); | |
119 | return 0; | |
120 | } | |
121 | ||
e8303bb7 AG |
122 | static struct pcie_port_service_driver pcie_bandwidth_notification_driver = { |
123 | .name = "pcie_bw_notification", | |
124 | .port_type = PCIE_ANY_PORT, | |
125 | .service = PCIE_PORT_SERVICE_BWNOTIF, | |
126 | .probe = pcie_bandwidth_notification_probe, | |
6056bed9 MW |
127 | .suspend = pcie_bandwidth_notification_suspend, |
128 | .resume = pcie_bandwidth_notification_resume, | |
e8303bb7 AG |
129 | .remove = pcie_bandwidth_notification_remove, |
130 | }; | |
131 | ||
132 | int __init pcie_bandwidth_notification_init(void) | |
133 | { | |
134 | return pcie_port_service_register(&pcie_bandwidth_notification_driver); | |
135 | } |