Commit 9c926ec7 authored by Bjorn Helgaas's avatar Bjorn Helgaas
Browse files

Merge branch 'pci/portdrv'

  - Allow portdrv to claim subtractive decode Ports so PCIe services will
    work for them (Honghui Zhang)

  - Report PCIe links that become degraded at run-time (Alexandru Gagniuc)

* pci/portdrv:
  PCI/LINK: Report degraded links via link bandwidth notification
  PCI/portdrv: Support PCIe services on subtractive decode bridges
  PCI/portdrv: Use conventional Device ID table formatting
parents 6d940a71 e8303bb7
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
# Makefile for PCI Express features and port driver

pcieportdrv-y			:= portdrv_core.o portdrv_pci.o err.o
pcieportdrv-y			+= bw_notification.o

obj-$(CONFIG_PCIEPORTBUS)	+= pcieportdrv.o

+110 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0+
/*
 * PCI Express Link Bandwidth Notification services driver
 * Author: Alexandru Gagniuc <mr.nuke.me@gmail.com>
 *
 * Copyright (C) 2019, Dell Inc
 *
 * The PCIe Link Bandwidth Notification provides a way to notify the
 * operating system when the link width or data rate changes.  This
 * capability is required for all root ports and downstream ports
 * supporting links wider than x1 and/or multiple link speeds.
 *
 * This service port driver hooks into the bandwidth notification interrupt
 * and warns when links become degraded in operation.
 */

#include "../pci.h"
#include "portdrv.h"

static bool pcie_link_bandwidth_notification_supported(struct pci_dev *dev)
{
	int ret;
	u32 lnk_cap;

	ret = pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &lnk_cap);
	return (ret == PCIBIOS_SUCCESSFUL) && (lnk_cap & PCI_EXP_LNKCAP_LBNC);
}

static void pcie_enable_link_bandwidth_notification(struct pci_dev *dev)
{
	u16 lnk_ctl;

	pcie_capability_read_word(dev, PCI_EXP_LNKCTL, &lnk_ctl);
	lnk_ctl |= PCI_EXP_LNKCTL_LBMIE;
	pcie_capability_write_word(dev, PCI_EXP_LNKCTL, lnk_ctl);
}

static void pcie_disable_link_bandwidth_notification(struct pci_dev *dev)
{
	u16 lnk_ctl;

	pcie_capability_read_word(dev, PCI_EXP_LNKCTL, &lnk_ctl);
	lnk_ctl &= ~PCI_EXP_LNKCTL_LBMIE;
	pcie_capability_write_word(dev, PCI_EXP_LNKCTL, lnk_ctl);
}

static irqreturn_t pcie_bw_notification_handler(int irq, void *context)
{
	struct pcie_device *srv = context;
	struct pci_dev *port = srv->port;
	struct pci_dev *dev;
	u16 link_status, events;
	int ret;

	ret = pcie_capability_read_word(port, PCI_EXP_LNKSTA, &link_status);
	events = link_status & PCI_EXP_LNKSTA_LBMS;

	if (ret != PCIBIOS_SUCCESSFUL || !events)
		return IRQ_NONE;

	/*
	 * Print status from downstream devices, not this root port or
	 * downstream switch port.
	 */
	down_read(&pci_bus_sem);
	list_for_each_entry(dev, &port->subordinate->devices, bus_list)
		__pcie_print_link_status(dev, false);
	up_read(&pci_bus_sem);

	pcie_update_link_speed(port->subordinate, link_status);
	pcie_capability_write_word(port, PCI_EXP_LNKSTA, events);
	return IRQ_HANDLED;
}

static int pcie_bandwidth_notification_probe(struct pcie_device *srv)
{
	int ret;

	/* Single-width or single-speed ports do not have to support this. */
	if (!pcie_link_bandwidth_notification_supported(srv->port))
		return -ENODEV;

	ret = request_threaded_irq(srv->irq, NULL, pcie_bw_notification_handler,
				   IRQF_SHARED, "PCIe BW notif", srv);
	if (ret)
		return ret;

	pcie_enable_link_bandwidth_notification(srv->port);

	return 0;
}

static void pcie_bandwidth_notification_remove(struct pcie_device *srv)
{
	pcie_disable_link_bandwidth_notification(srv->port);
	free_irq(srv->irq, srv);
}

static struct pcie_port_service_driver pcie_bandwidth_notification_driver = {
	.name		= "pcie_bw_notification",
	.port_type	= PCIE_ANY_PORT,
	.service	= PCIE_PORT_SERVICE_BWNOTIF,
	.probe		= pcie_bandwidth_notification_probe,
	.remove		= pcie_bandwidth_notification_remove,
};

int __init pcie_bandwidth_notification_init(void)
{
	return pcie_port_service_register(&pcie_bandwidth_notification_driver);
}
+5 −1
Original line number Diff line number Diff line
@@ -20,8 +20,10 @@
#define PCIE_PORT_SERVICE_HP		(1 << PCIE_PORT_SERVICE_HP_SHIFT)
#define PCIE_PORT_SERVICE_DPC_SHIFT	3	/* Downstream Port Containment */
#define PCIE_PORT_SERVICE_DPC		(1 << PCIE_PORT_SERVICE_DPC_SHIFT)
#define PCIE_PORT_SERVICE_BWNOTIF_SHIFT	4	/* Bandwidth notification */
#define PCIE_PORT_SERVICE_BWNOTIF	(1 << PCIE_PORT_SERVICE_BWNOTIF_SHIFT)

#define PCIE_PORT_DEVICE_MAXSERVICES   4
#define PCIE_PORT_DEVICE_MAXSERVICES   5

#ifdef CONFIG_PCIEAER
int pcie_aer_init(void);
@@ -47,6 +49,8 @@ int pcie_dpc_init(void);
static inline int pcie_dpc_init(void) { return 0; }
#endif

int pcie_bandwidth_notification_init(void);

/* Port Type */
#define PCIE_ANY_PORT			(~0)

+12 −5
Original line number Diff line number Diff line
@@ -99,7 +99,7 @@ static int pcie_message_numbers(struct pci_dev *dev, int mask,
 */
static int pcie_port_enable_irq_vec(struct pci_dev *dev, int *irqs, int mask)
{
	int nr_entries, nvec;
	int nr_entries, nvec, pcie_irq;
	u32 pme = 0, aer = 0, dpc = 0;

	/* Allocate the maximum possible number of MSI/MSI-X vectors */
@@ -135,10 +135,13 @@ static int pcie_port_enable_irq_vec(struct pci_dev *dev, int *irqs, int mask)
			return nr_entries;
	}

	/* PME and hotplug share an MSI/MSI-X vector */
	if (mask & (PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP)) {
		irqs[PCIE_PORT_SERVICE_PME_SHIFT] = pci_irq_vector(dev, pme);
		irqs[PCIE_PORT_SERVICE_HP_SHIFT] = pci_irq_vector(dev, pme);
	/* PME, hotplug and bandwidth notification share an MSI/MSI-X vector */
	if (mask & (PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP |
		    PCIE_PORT_SERVICE_BWNOTIF)) {
		pcie_irq = pci_irq_vector(dev, pme);
		irqs[PCIE_PORT_SERVICE_PME_SHIFT] = pcie_irq;
		irqs[PCIE_PORT_SERVICE_HP_SHIFT] = pcie_irq;
		irqs[PCIE_PORT_SERVICE_BWNOTIF_SHIFT] = pcie_irq;
	}

	if (mask & PCIE_PORT_SERVICE_AER)
@@ -250,6 +253,10 @@ static int get_port_device_capability(struct pci_dev *dev)
	    pci_aer_available() && services & PCIE_PORT_SERVICE_AER)
		services |= PCIE_PORT_SERVICE_DPC;

	if (pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM ||
	    pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT)
		services |= PCIE_PORT_SERVICE_BWNOTIF;

	return services;
}

+6 −3
Original line number Diff line number Diff line
@@ -182,10 +182,12 @@ static void pcie_portdrv_err_resume(struct pci_dev *dev)
/*
 * LINUX Device Driver Model
 */
static const struct pci_device_id port_pci_ids[] = { {
static const struct pci_device_id port_pci_ids[] = {
	/* handle any PCI-Express port */
	PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), ~0),
	}, { /* end: all zeroes */ }
	{ PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), ~0) },
	/* subtractive decode PCI-to-PCI bridge, class type is 060401h */
	{ PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x01), ~0) },
	{ },
};

static const struct pci_error_handlers pcie_portdrv_err_handler = {
@@ -238,6 +240,7 @@ static void __init pcie_init_services(void)
	pcie_pme_init();
	pcie_dpc_init();
	pcie_hp_init();
	pcie_bandwidth_notification_init();
}

static int __init pcie_portdrv_init(void)