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