1*4882a593Smuzhiyun // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Copyright(c) 2019 Intel Corporation.
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun */
6*4882a593Smuzhiyun
7*4882a593Smuzhiyun #include "aspm.h"
8*4882a593Smuzhiyun
9*4882a593Smuzhiyun /* Time after which the timer interrupt will re-enable ASPM */
10*4882a593Smuzhiyun #define ASPM_TIMER_MS 1000
11*4882a593Smuzhiyun /* Time for which interrupts are ignored after a timer has been scheduled */
12*4882a593Smuzhiyun #define ASPM_RESCHED_TIMER_MS (ASPM_TIMER_MS / 2)
13*4882a593Smuzhiyun /* Two interrupts within this time trigger ASPM disable */
14*4882a593Smuzhiyun #define ASPM_TRIGGER_MS 1
15*4882a593Smuzhiyun #define ASPM_TRIGGER_NS (ASPM_TRIGGER_MS * 1000 * 1000ull)
16*4882a593Smuzhiyun #define ASPM_L1_SUPPORTED(reg) \
17*4882a593Smuzhiyun ((((reg) & PCI_EXP_LNKCAP_ASPMS) >> 10) & 0x2)
18*4882a593Smuzhiyun
19*4882a593Smuzhiyun uint aspm_mode = ASPM_MODE_DISABLED;
20*4882a593Smuzhiyun module_param_named(aspm, aspm_mode, uint, 0444);
21*4882a593Smuzhiyun MODULE_PARM_DESC(aspm, "PCIe ASPM: 0: disable, 1: enable, 2: dynamic");
22*4882a593Smuzhiyun
aspm_hw_l1_supported(struct hfi1_devdata * dd)23*4882a593Smuzhiyun static bool aspm_hw_l1_supported(struct hfi1_devdata *dd)
24*4882a593Smuzhiyun {
25*4882a593Smuzhiyun struct pci_dev *parent = dd->pcidev->bus->self;
26*4882a593Smuzhiyun u32 up, dn;
27*4882a593Smuzhiyun
28*4882a593Smuzhiyun /*
29*4882a593Smuzhiyun * If the driver does not have access to the upstream component,
30*4882a593Smuzhiyun * it cannot support ASPM L1 at all.
31*4882a593Smuzhiyun */
32*4882a593Smuzhiyun if (!parent)
33*4882a593Smuzhiyun return false;
34*4882a593Smuzhiyun
35*4882a593Smuzhiyun pcie_capability_read_dword(dd->pcidev, PCI_EXP_LNKCAP, &dn);
36*4882a593Smuzhiyun dn = ASPM_L1_SUPPORTED(dn);
37*4882a593Smuzhiyun
38*4882a593Smuzhiyun pcie_capability_read_dword(parent, PCI_EXP_LNKCAP, &up);
39*4882a593Smuzhiyun up = ASPM_L1_SUPPORTED(up);
40*4882a593Smuzhiyun
41*4882a593Smuzhiyun /* ASPM works on A-step but is reported as not supported */
42*4882a593Smuzhiyun return (!!dn || is_ax(dd)) && !!up;
43*4882a593Smuzhiyun }
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun /* Set L1 entrance latency for slower entry to L1 */
aspm_hw_set_l1_ent_latency(struct hfi1_devdata * dd)46*4882a593Smuzhiyun static void aspm_hw_set_l1_ent_latency(struct hfi1_devdata *dd)
47*4882a593Smuzhiyun {
48*4882a593Smuzhiyun u32 l1_ent_lat = 0x4u;
49*4882a593Smuzhiyun u32 reg32;
50*4882a593Smuzhiyun
51*4882a593Smuzhiyun pci_read_config_dword(dd->pcidev, PCIE_CFG_REG_PL3, ®32);
52*4882a593Smuzhiyun reg32 &= ~PCIE_CFG_REG_PL3_L1_ENT_LATENCY_SMASK;
53*4882a593Smuzhiyun reg32 |= l1_ent_lat << PCIE_CFG_REG_PL3_L1_ENT_LATENCY_SHIFT;
54*4882a593Smuzhiyun pci_write_config_dword(dd->pcidev, PCIE_CFG_REG_PL3, reg32);
55*4882a593Smuzhiyun }
56*4882a593Smuzhiyun
aspm_hw_enable_l1(struct hfi1_devdata * dd)57*4882a593Smuzhiyun static void aspm_hw_enable_l1(struct hfi1_devdata *dd)
58*4882a593Smuzhiyun {
59*4882a593Smuzhiyun struct pci_dev *parent = dd->pcidev->bus->self;
60*4882a593Smuzhiyun
61*4882a593Smuzhiyun /*
62*4882a593Smuzhiyun * If the driver does not have access to the upstream component,
63*4882a593Smuzhiyun * it cannot support ASPM L1 at all.
64*4882a593Smuzhiyun */
65*4882a593Smuzhiyun if (!parent)
66*4882a593Smuzhiyun return;
67*4882a593Smuzhiyun
68*4882a593Smuzhiyun /* Enable ASPM L1 first in upstream component and then downstream */
69*4882a593Smuzhiyun pcie_capability_clear_and_set_word(parent, PCI_EXP_LNKCTL,
70*4882a593Smuzhiyun PCI_EXP_LNKCTL_ASPMC,
71*4882a593Smuzhiyun PCI_EXP_LNKCTL_ASPM_L1);
72*4882a593Smuzhiyun pcie_capability_clear_and_set_word(dd->pcidev, PCI_EXP_LNKCTL,
73*4882a593Smuzhiyun PCI_EXP_LNKCTL_ASPMC,
74*4882a593Smuzhiyun PCI_EXP_LNKCTL_ASPM_L1);
75*4882a593Smuzhiyun }
76*4882a593Smuzhiyun
aspm_hw_disable_l1(struct hfi1_devdata * dd)77*4882a593Smuzhiyun void aspm_hw_disable_l1(struct hfi1_devdata *dd)
78*4882a593Smuzhiyun {
79*4882a593Smuzhiyun struct pci_dev *parent = dd->pcidev->bus->self;
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun /* Disable ASPM L1 first in downstream component and then upstream */
82*4882a593Smuzhiyun pcie_capability_clear_and_set_word(dd->pcidev, PCI_EXP_LNKCTL,
83*4882a593Smuzhiyun PCI_EXP_LNKCTL_ASPMC, 0x0);
84*4882a593Smuzhiyun if (parent)
85*4882a593Smuzhiyun pcie_capability_clear_and_set_word(parent, PCI_EXP_LNKCTL,
86*4882a593Smuzhiyun PCI_EXP_LNKCTL_ASPMC, 0x0);
87*4882a593Smuzhiyun }
88*4882a593Smuzhiyun
aspm_enable(struct hfi1_devdata * dd)89*4882a593Smuzhiyun static void aspm_enable(struct hfi1_devdata *dd)
90*4882a593Smuzhiyun {
91*4882a593Smuzhiyun if (dd->aspm_enabled || aspm_mode == ASPM_MODE_DISABLED ||
92*4882a593Smuzhiyun !dd->aspm_supported)
93*4882a593Smuzhiyun return;
94*4882a593Smuzhiyun
95*4882a593Smuzhiyun aspm_hw_enable_l1(dd);
96*4882a593Smuzhiyun dd->aspm_enabled = true;
97*4882a593Smuzhiyun }
98*4882a593Smuzhiyun
aspm_disable(struct hfi1_devdata * dd)99*4882a593Smuzhiyun static void aspm_disable(struct hfi1_devdata *dd)
100*4882a593Smuzhiyun {
101*4882a593Smuzhiyun if (!dd->aspm_enabled || aspm_mode == ASPM_MODE_ENABLED)
102*4882a593Smuzhiyun return;
103*4882a593Smuzhiyun
104*4882a593Smuzhiyun aspm_hw_disable_l1(dd);
105*4882a593Smuzhiyun dd->aspm_enabled = false;
106*4882a593Smuzhiyun }
107*4882a593Smuzhiyun
aspm_disable_inc(struct hfi1_devdata * dd)108*4882a593Smuzhiyun static void aspm_disable_inc(struct hfi1_devdata *dd)
109*4882a593Smuzhiyun {
110*4882a593Smuzhiyun unsigned long flags;
111*4882a593Smuzhiyun
112*4882a593Smuzhiyun spin_lock_irqsave(&dd->aspm_lock, flags);
113*4882a593Smuzhiyun aspm_disable(dd);
114*4882a593Smuzhiyun atomic_inc(&dd->aspm_disabled_cnt);
115*4882a593Smuzhiyun spin_unlock_irqrestore(&dd->aspm_lock, flags);
116*4882a593Smuzhiyun }
117*4882a593Smuzhiyun
aspm_enable_dec(struct hfi1_devdata * dd)118*4882a593Smuzhiyun static void aspm_enable_dec(struct hfi1_devdata *dd)
119*4882a593Smuzhiyun {
120*4882a593Smuzhiyun unsigned long flags;
121*4882a593Smuzhiyun
122*4882a593Smuzhiyun spin_lock_irqsave(&dd->aspm_lock, flags);
123*4882a593Smuzhiyun if (atomic_dec_and_test(&dd->aspm_disabled_cnt))
124*4882a593Smuzhiyun aspm_enable(dd);
125*4882a593Smuzhiyun spin_unlock_irqrestore(&dd->aspm_lock, flags);
126*4882a593Smuzhiyun }
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun /* ASPM processing for each receive context interrupt */
__aspm_ctx_disable(struct hfi1_ctxtdata * rcd)129*4882a593Smuzhiyun void __aspm_ctx_disable(struct hfi1_ctxtdata *rcd)
130*4882a593Smuzhiyun {
131*4882a593Smuzhiyun bool restart_timer;
132*4882a593Smuzhiyun bool close_interrupts;
133*4882a593Smuzhiyun unsigned long flags;
134*4882a593Smuzhiyun ktime_t now, prev;
135*4882a593Smuzhiyun
136*4882a593Smuzhiyun spin_lock_irqsave(&rcd->aspm_lock, flags);
137*4882a593Smuzhiyun /* PSM contexts are open */
138*4882a593Smuzhiyun if (!rcd->aspm_intr_enable)
139*4882a593Smuzhiyun goto unlock;
140*4882a593Smuzhiyun
141*4882a593Smuzhiyun prev = rcd->aspm_ts_last_intr;
142*4882a593Smuzhiyun now = ktime_get();
143*4882a593Smuzhiyun rcd->aspm_ts_last_intr = now;
144*4882a593Smuzhiyun
145*4882a593Smuzhiyun /* An interrupt pair close together in time */
146*4882a593Smuzhiyun close_interrupts = ktime_to_ns(ktime_sub(now, prev)) < ASPM_TRIGGER_NS;
147*4882a593Smuzhiyun
148*4882a593Smuzhiyun /* Don't push out our timer till this much time has elapsed */
149*4882a593Smuzhiyun restart_timer = ktime_to_ns(ktime_sub(now, rcd->aspm_ts_timer_sched)) >
150*4882a593Smuzhiyun ASPM_RESCHED_TIMER_MS * NSEC_PER_MSEC;
151*4882a593Smuzhiyun restart_timer = restart_timer && close_interrupts;
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun /* Disable ASPM and schedule timer */
154*4882a593Smuzhiyun if (rcd->aspm_enabled && close_interrupts) {
155*4882a593Smuzhiyun aspm_disable_inc(rcd->dd);
156*4882a593Smuzhiyun rcd->aspm_enabled = false;
157*4882a593Smuzhiyun restart_timer = true;
158*4882a593Smuzhiyun }
159*4882a593Smuzhiyun
160*4882a593Smuzhiyun if (restart_timer) {
161*4882a593Smuzhiyun mod_timer(&rcd->aspm_timer,
162*4882a593Smuzhiyun jiffies + msecs_to_jiffies(ASPM_TIMER_MS));
163*4882a593Smuzhiyun rcd->aspm_ts_timer_sched = now;
164*4882a593Smuzhiyun }
165*4882a593Smuzhiyun unlock:
166*4882a593Smuzhiyun spin_unlock_irqrestore(&rcd->aspm_lock, flags);
167*4882a593Smuzhiyun }
168*4882a593Smuzhiyun
169*4882a593Smuzhiyun /* Timer function for re-enabling ASPM in the absence of interrupt activity */
aspm_ctx_timer_function(struct timer_list * t)170*4882a593Smuzhiyun static void aspm_ctx_timer_function(struct timer_list *t)
171*4882a593Smuzhiyun {
172*4882a593Smuzhiyun struct hfi1_ctxtdata *rcd = from_timer(rcd, t, aspm_timer);
173*4882a593Smuzhiyun unsigned long flags;
174*4882a593Smuzhiyun
175*4882a593Smuzhiyun spin_lock_irqsave(&rcd->aspm_lock, flags);
176*4882a593Smuzhiyun aspm_enable_dec(rcd->dd);
177*4882a593Smuzhiyun rcd->aspm_enabled = true;
178*4882a593Smuzhiyun spin_unlock_irqrestore(&rcd->aspm_lock, flags);
179*4882a593Smuzhiyun }
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun /*
182*4882a593Smuzhiyun * Disable interrupt processing for verbs contexts when PSM or VNIC contexts
183*4882a593Smuzhiyun * are open.
184*4882a593Smuzhiyun */
aspm_disable_all(struct hfi1_devdata * dd)185*4882a593Smuzhiyun void aspm_disable_all(struct hfi1_devdata *dd)
186*4882a593Smuzhiyun {
187*4882a593Smuzhiyun struct hfi1_ctxtdata *rcd;
188*4882a593Smuzhiyun unsigned long flags;
189*4882a593Smuzhiyun u16 i;
190*4882a593Smuzhiyun
191*4882a593Smuzhiyun for (i = 0; i < dd->first_dyn_alloc_ctxt; i++) {
192*4882a593Smuzhiyun rcd = hfi1_rcd_get_by_index(dd, i);
193*4882a593Smuzhiyun if (rcd) {
194*4882a593Smuzhiyun del_timer_sync(&rcd->aspm_timer);
195*4882a593Smuzhiyun spin_lock_irqsave(&rcd->aspm_lock, flags);
196*4882a593Smuzhiyun rcd->aspm_intr_enable = false;
197*4882a593Smuzhiyun spin_unlock_irqrestore(&rcd->aspm_lock, flags);
198*4882a593Smuzhiyun hfi1_rcd_put(rcd);
199*4882a593Smuzhiyun }
200*4882a593Smuzhiyun }
201*4882a593Smuzhiyun
202*4882a593Smuzhiyun aspm_disable(dd);
203*4882a593Smuzhiyun atomic_set(&dd->aspm_disabled_cnt, 0);
204*4882a593Smuzhiyun }
205*4882a593Smuzhiyun
206*4882a593Smuzhiyun /* Re-enable interrupt processing for verbs contexts */
aspm_enable_all(struct hfi1_devdata * dd)207*4882a593Smuzhiyun void aspm_enable_all(struct hfi1_devdata *dd)
208*4882a593Smuzhiyun {
209*4882a593Smuzhiyun struct hfi1_ctxtdata *rcd;
210*4882a593Smuzhiyun unsigned long flags;
211*4882a593Smuzhiyun u16 i;
212*4882a593Smuzhiyun
213*4882a593Smuzhiyun aspm_enable(dd);
214*4882a593Smuzhiyun
215*4882a593Smuzhiyun if (aspm_mode != ASPM_MODE_DYNAMIC)
216*4882a593Smuzhiyun return;
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun for (i = 0; i < dd->first_dyn_alloc_ctxt; i++) {
219*4882a593Smuzhiyun rcd = hfi1_rcd_get_by_index(dd, i);
220*4882a593Smuzhiyun if (rcd) {
221*4882a593Smuzhiyun spin_lock_irqsave(&rcd->aspm_lock, flags);
222*4882a593Smuzhiyun rcd->aspm_intr_enable = true;
223*4882a593Smuzhiyun rcd->aspm_enabled = true;
224*4882a593Smuzhiyun spin_unlock_irqrestore(&rcd->aspm_lock, flags);
225*4882a593Smuzhiyun hfi1_rcd_put(rcd);
226*4882a593Smuzhiyun }
227*4882a593Smuzhiyun }
228*4882a593Smuzhiyun }
229*4882a593Smuzhiyun
aspm_ctx_init(struct hfi1_ctxtdata * rcd)230*4882a593Smuzhiyun static void aspm_ctx_init(struct hfi1_ctxtdata *rcd)
231*4882a593Smuzhiyun {
232*4882a593Smuzhiyun spin_lock_init(&rcd->aspm_lock);
233*4882a593Smuzhiyun timer_setup(&rcd->aspm_timer, aspm_ctx_timer_function, 0);
234*4882a593Smuzhiyun rcd->aspm_intr_supported = rcd->dd->aspm_supported &&
235*4882a593Smuzhiyun aspm_mode == ASPM_MODE_DYNAMIC &&
236*4882a593Smuzhiyun rcd->ctxt < rcd->dd->first_dyn_alloc_ctxt;
237*4882a593Smuzhiyun }
238*4882a593Smuzhiyun
aspm_init(struct hfi1_devdata * dd)239*4882a593Smuzhiyun void aspm_init(struct hfi1_devdata *dd)
240*4882a593Smuzhiyun {
241*4882a593Smuzhiyun struct hfi1_ctxtdata *rcd;
242*4882a593Smuzhiyun u16 i;
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun spin_lock_init(&dd->aspm_lock);
245*4882a593Smuzhiyun dd->aspm_supported = aspm_hw_l1_supported(dd);
246*4882a593Smuzhiyun
247*4882a593Smuzhiyun for (i = 0; i < dd->first_dyn_alloc_ctxt; i++) {
248*4882a593Smuzhiyun rcd = hfi1_rcd_get_by_index(dd, i);
249*4882a593Smuzhiyun if (rcd)
250*4882a593Smuzhiyun aspm_ctx_init(rcd);
251*4882a593Smuzhiyun hfi1_rcd_put(rcd);
252*4882a593Smuzhiyun }
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun /* Start with ASPM disabled */
255*4882a593Smuzhiyun aspm_hw_set_l1_ent_latency(dd);
256*4882a593Smuzhiyun dd->aspm_enabled = false;
257*4882a593Smuzhiyun aspm_hw_disable_l1(dd);
258*4882a593Smuzhiyun
259*4882a593Smuzhiyun /* Now turn on ASPM if configured */
260*4882a593Smuzhiyun aspm_enable_all(dd);
261*4882a593Smuzhiyun }
262*4882a593Smuzhiyun
aspm_exit(struct hfi1_devdata * dd)263*4882a593Smuzhiyun void aspm_exit(struct hfi1_devdata *dd)
264*4882a593Smuzhiyun {
265*4882a593Smuzhiyun aspm_disable_all(dd);
266*4882a593Smuzhiyun
267*4882a593Smuzhiyun /* Turn on ASPM on exit to conserve power */
268*4882a593Smuzhiyun aspm_enable(dd);
269*4882a593Smuzhiyun }
270*4882a593Smuzhiyun
271