linux/drivers/pci/pcie/bw_notification.c
<<
>>
Prefs
   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#define dev_fmt(fmt) "bw_notification: " fmt
  18
  19#include "../pci.h"
  20#include "portdrv.h"
  21
  22static bool pcie_link_bandwidth_notification_supported(struct pci_dev *dev)
  23{
  24        int ret;
  25        u32 lnk_cap;
  26
  27        ret = pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &lnk_cap);
  28        return (ret == PCIBIOS_SUCCESSFUL) && (lnk_cap & PCI_EXP_LNKCAP_LBNC);
  29}
  30
  31static void pcie_enable_link_bandwidth_notification(struct pci_dev *dev)
  32{
  33        u16 lnk_ctl;
  34
  35        pcie_capability_write_word(dev, PCI_EXP_LNKSTA, PCI_EXP_LNKSTA_LBMS);
  36
  37        pcie_capability_read_word(dev, PCI_EXP_LNKCTL, &lnk_ctl);
  38        lnk_ctl |= PCI_EXP_LNKCTL_LBMIE;
  39        pcie_capability_write_word(dev, PCI_EXP_LNKCTL, lnk_ctl);
  40}
  41
  42static void pcie_disable_link_bandwidth_notification(struct pci_dev *dev)
  43{
  44        u16 lnk_ctl;
  45
  46        pcie_capability_read_word(dev, PCI_EXP_LNKCTL, &lnk_ctl);
  47        lnk_ctl &= ~PCI_EXP_LNKCTL_LBMIE;
  48        pcie_capability_write_word(dev, PCI_EXP_LNKCTL, lnk_ctl);
  49}
  50
  51static irqreturn_t pcie_bw_notification_irq(int irq, void *context)
  52{
  53        struct pcie_device *srv = context;
  54        struct pci_dev *port = srv->port;
  55        u16 link_status, events;
  56        int ret;
  57
  58        ret = pcie_capability_read_word(port, PCI_EXP_LNKSTA, &link_status);
  59        events = link_status & PCI_EXP_LNKSTA_LBMS;
  60
  61        if (ret != PCIBIOS_SUCCESSFUL || !events)
  62                return IRQ_NONE;
  63
  64        pcie_capability_write_word(port, PCI_EXP_LNKSTA, events);
  65        pcie_update_link_speed(port->subordinate, link_status);
  66        return IRQ_WAKE_THREAD;
  67}
  68
  69static irqreturn_t pcie_bw_notification_handler(int irq, void *context)
  70{
  71        struct pcie_device *srv = context;
  72        struct pci_dev *port = srv->port;
  73        struct pci_dev *dev;
  74
  75        /*
  76         * Print status from downstream devices, not this root port or
  77         * downstream switch port.
  78         */
  79        down_read(&pci_bus_sem);
  80        list_for_each_entry(dev, &port->subordinate->devices, bus_list)
  81                pcie_report_downtraining(dev);
  82        up_read(&pci_bus_sem);
  83
  84        return IRQ_HANDLED;
  85}
  86
  87static int pcie_bandwidth_notification_probe(struct pcie_device *srv)
  88{
  89        int ret;
  90
  91        /* Single-width or single-speed ports do not have to support this. */
  92        if (!pcie_link_bandwidth_notification_supported(srv->port))
  93                return -ENODEV;
  94
  95        ret = request_threaded_irq(srv->irq, pcie_bw_notification_irq,
  96                                   pcie_bw_notification_handler,
  97                                   IRQF_SHARED, "PCIe BW notif", srv);
  98        if (ret)
  99                return ret;
 100
 101        pcie_enable_link_bandwidth_notification(srv->port);
 102        pci_info(srv->port, "enabled with IRQ %d\n", srv->irq);
 103
 104        return 0;
 105}
 106
 107static void pcie_bandwidth_notification_remove(struct pcie_device *srv)
 108{
 109        pcie_disable_link_bandwidth_notification(srv->port);
 110        free_irq(srv->irq, srv);
 111}
 112
 113static int pcie_bandwidth_notification_suspend(struct pcie_device *srv)
 114{
 115        pcie_disable_link_bandwidth_notification(srv->port);
 116        return 0;
 117}
 118
 119static int pcie_bandwidth_notification_resume(struct pcie_device *srv)
 120{
 121        pcie_enable_link_bandwidth_notification(srv->port);
 122        return 0;
 123}
 124
 125static struct pcie_port_service_driver pcie_bandwidth_notification_driver = {
 126        .name           = "pcie_bw_notification",
 127        .port_type      = PCIE_ANY_PORT,
 128        .service        = PCIE_PORT_SERVICE_BWNOTIF,
 129        .probe          = pcie_bandwidth_notification_probe,
 130        .suspend        = pcie_bandwidth_notification_suspend,
 131        .resume         = pcie_bandwidth_notification_resume,
 132        .remove         = pcie_bandwidth_notification_remove,
 133};
 134
 135int __init pcie_bandwidth_notification_init(void)
 136{
 137        return pcie_port_service_register(&pcie_bandwidth_notification_driver);
 138}
 139