Linux Audio

Check our new training course

Real-Time Linux with PREEMPT_RT training

Feb 18-20, 2025
Register
Loading...
Note: File does not exist in v3.1.
  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
 20static 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
 29static void pcie_enable_link_bandwidth_notification(struct pci_dev *dev)
 30{
 31	u16 lnk_ctl;
 32
 33	pcie_capability_write_word(dev, PCI_EXP_LNKSTA, PCI_EXP_LNKSTA_LBMS);
 34
 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
 40static 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
 49static irqreturn_t pcie_bw_notification_irq(int irq, void *context)
 50{
 51	struct pcie_device *srv = context;
 52	struct pci_dev *port = srv->port;
 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
 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
 67static 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
 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)
 79		pcie_report_downtraining(dev);
 80	up_read(&pci_bus_sem);
 81
 82	return IRQ_HANDLED;
 83}
 84
 85static 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
 93	ret = request_threaded_irq(srv->irq, pcie_bw_notification_irq,
 94				   pcie_bw_notification_handler,
 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
104static 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
110static int pcie_bandwidth_notification_suspend(struct pcie_device *srv)
111{
112	pcie_disable_link_bandwidth_notification(srv->port);
113	return 0;
114}
115
116static int pcie_bandwidth_notification_resume(struct pcie_device *srv)
117{
118	pcie_enable_link_bandwidth_notification(srv->port);
119	return 0;
120}
121
122static 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,
127	.suspend	= pcie_bandwidth_notification_suspend,
128	.resume		= pcie_bandwidth_notification_resume,
129	.remove		= pcie_bandwidth_notification_remove,
130};
131
132int __init pcie_bandwidth_notification_init(void)
133{
134	return pcie_port_service_register(&pcie_bandwidth_notification_driver);
135}