Skip to content

Commit

Permalink
Merge branch 'pci/portdrv'
Browse files Browse the repository at this point in the history
- Squash portdrv_core.c and portdrv_pci.c into portdrv.c to make it easier
  to find things (Bjorn Helgaas)

- Allow AER service only for Root Ports & RCECs so portdrv can successfully
  bind to other devices that have AER but lack MSI (which they don't need
  for AER), which allows power management for those devices (Bjorn Helgaas)

* pci/portdrv:
  PCI/portdrv: Allow AER service only for Root Ports & RCECs
  PCI/portdrv: Unexport pcie_port_service_register(), pcie_port_service_unregister()
  PCI/portdrv: Move private things to portdrv.c
  PCI/portdrv: Squash into portdrv.c
  • Loading branch information
bjorn-helgaas committed Dec 10, 2022
2 parents ec7c9a6 + d8d2b65 commit 9303050
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 284 deletions.
2 changes: 1 addition & 1 deletion drivers/pci/pcie/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# Makefile for PCI Express features and port driver

pcieportdrv-y := portdrv_core.o portdrv_pci.o rcec.o
pcieportdrv-y := portdrv.o rcec.o

obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o

Expand Down
267 changes: 255 additions & 12 deletions drivers/pci/pcie/portdrv_core.c → drivers/pci/pcie/portdrv.c
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Purpose: PCI Express Port Bus Driver's Core Functions
* Purpose: PCI Express Port Bus Driver
*
* Copyright (C) 2004 Intel
* Copyright (C) Tom Long Nguyen ([email protected])
*/

#include <linux/dmi.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
Expand All @@ -19,6 +21,15 @@
#include "../pci.h"
#include "portdrv.h"

/*
* The PCIe Capability Interrupt Message Number (PCIe r3.1, sec 7.8.2) must
* be one of the first 32 MSI-X entries. Per PCI r3.0, sec 6.8.3.1, MSI
* supports a maximum of 32 vectors per function.
*/
#define PCIE_PORT_MAX_MSI_ENTRIES 32

#define get_descriptor_id(type, service) (((type - 4) << 8) | service)

struct portdrv_service_data {
struct pcie_port_service_driver *drv;
struct device *dev;
Expand Down Expand Up @@ -223,7 +234,9 @@ static int get_port_device_capability(struct pci_dev *dev)
}

#ifdef CONFIG_PCIEAER
if (dev->aer_cap && pci_aer_available() &&
if ((pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT ||
pci_pcie_type(dev) == PCI_EXP_TYPE_RC_EC) &&
dev->aer_cap && pci_aer_available() &&
(pcie_ports_native || host->native_aer))
services |= PCIE_PORT_SERVICE_AER;
#endif
Expand Down Expand Up @@ -310,7 +323,7 @@ static int pcie_device_init(struct pci_dev *pdev, int service, int irq)
* Allocate the port extension structure and register services associated with
* the port.
*/
int pcie_port_device_register(struct pci_dev *dev)
static int pcie_port_device_register(struct pci_dev *dev)
{
int status, capabilities, i, nr_service;
int irqs[PCIE_PORT_DEVICE_MAXSERVICES];
Expand Down Expand Up @@ -364,7 +377,7 @@ int pcie_port_device_register(struct pci_dev *dev)

typedef int (*pcie_callback_t)(struct pcie_device *);

int pcie_port_device_iter(struct device *dev, void *data)
static int pcie_port_device_iter(struct device *dev, void *data)
{
struct pcie_port_service_driver *service_driver;
size_t offset = *(size_t *)data;
Expand All @@ -384,13 +397,13 @@ int pcie_port_device_iter(struct device *dev, void *data)
* pcie_port_device_suspend - suspend port services associated with a PCIe port
* @dev: PCI Express port to handle
*/
int pcie_port_device_suspend(struct device *dev)
static int pcie_port_device_suspend(struct device *dev)
{
size_t off = offsetof(struct pcie_port_service_driver, suspend);
return device_for_each_child(dev, &off, pcie_port_device_iter);
}

int pcie_port_device_resume_noirq(struct device *dev)
static int pcie_port_device_resume_noirq(struct device *dev)
{
size_t off = offsetof(struct pcie_port_service_driver, resume_noirq);
return device_for_each_child(dev, &off, pcie_port_device_iter);
Expand All @@ -400,7 +413,7 @@ int pcie_port_device_resume_noirq(struct device *dev)
* pcie_port_device_resume - resume port services associated with a PCIe port
* @dev: PCI Express port to handle
*/
int pcie_port_device_resume(struct device *dev)
static int pcie_port_device_resume(struct device *dev)
{
size_t off = offsetof(struct pcie_port_service_driver, resume);
return device_for_each_child(dev, &off, pcie_port_device_iter);
Expand All @@ -410,7 +423,7 @@ int pcie_port_device_resume(struct device *dev)
* pcie_port_device_runtime_suspend - runtime suspend port services
* @dev: PCI Express port to handle
*/
int pcie_port_device_runtime_suspend(struct device *dev)
static int pcie_port_device_runtime_suspend(struct device *dev)
{
size_t off = offsetof(struct pcie_port_service_driver, runtime_suspend);
return device_for_each_child(dev, &off, pcie_port_device_iter);
Expand All @@ -420,7 +433,7 @@ int pcie_port_device_runtime_suspend(struct device *dev)
* pcie_port_device_runtime_resume - runtime resume port services
* @dev: PCI Express port to handle
*/
int pcie_port_device_runtime_resume(struct device *dev)
static int pcie_port_device_runtime_resume(struct device *dev)
{
size_t off = offsetof(struct pcie_port_service_driver, runtime_resume);
return device_for_each_child(dev, &off, pcie_port_device_iter);
Expand Down Expand Up @@ -484,7 +497,7 @@ EXPORT_SYMBOL_GPL(pcie_port_find_device);
* Remove PCI Express port service devices associated with given port and
* disable MSI-X or MSI for the port.
*/
void pcie_port_device_remove(struct pci_dev *dev)
static void pcie_port_device_remove(struct pci_dev *dev)
{
device_for_each_child(&dev->dev, NULL, remove_iter);
pci_free_irq_vectors(dev);
Expand Down Expand Up @@ -575,7 +588,6 @@ int pcie_port_service_register(struct pcie_port_service_driver *new)

return driver_register(&new->driver);
}
EXPORT_SYMBOL(pcie_port_service_register);

/**
* pcie_port_service_unregister - unregister PCI Express port service driver
Expand All @@ -585,4 +597,235 @@ void pcie_port_service_unregister(struct pcie_port_service_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL(pcie_port_service_unregister);

/* If this switch is set, PCIe port native services should not be enabled. */
bool pcie_ports_disabled;

/*
* If the user specified "pcie_ports=native", use the PCIe services regardless
* of whether the platform has given us permission. On ACPI systems, this
* means we ignore _OSC.
*/
bool pcie_ports_native;

/*
* If the user specified "pcie_ports=dpc-native", use the Linux DPC PCIe
* service even if the platform hasn't given us permission.
*/
bool pcie_ports_dpc_native;

static int __init pcie_port_setup(char *str)
{
if (!strncmp(str, "compat", 6))
pcie_ports_disabled = true;
else if (!strncmp(str, "native", 6))
pcie_ports_native = true;
else if (!strncmp(str, "dpc-native", 10))
pcie_ports_dpc_native = true;

return 1;
}
__setup("pcie_ports=", pcie_port_setup);

/* global data */

#ifdef CONFIG_PM
static int pcie_port_runtime_suspend(struct device *dev)
{
if (!to_pci_dev(dev)->bridge_d3)
return -EBUSY;

return pcie_port_device_runtime_suspend(dev);
}

static int pcie_port_runtime_idle(struct device *dev)
{
/*
* Assume the PCI core has set bridge_d3 whenever it thinks the port
* should be good to go to D3. Everything else, including moving
* the port to D3, is handled by the PCI core.
*/
return to_pci_dev(dev)->bridge_d3 ? 0 : -EBUSY;
}

static const struct dev_pm_ops pcie_portdrv_pm_ops = {
.suspend = pcie_port_device_suspend,
.resume_noirq = pcie_port_device_resume_noirq,
.resume = pcie_port_device_resume,
.freeze = pcie_port_device_suspend,
.thaw = pcie_port_device_resume,
.poweroff = pcie_port_device_suspend,
.restore_noirq = pcie_port_device_resume_noirq,
.restore = pcie_port_device_resume,
.runtime_suspend = pcie_port_runtime_suspend,
.runtime_resume = pcie_port_device_runtime_resume,
.runtime_idle = pcie_port_runtime_idle,
};

#define PCIE_PORTDRV_PM_OPS (&pcie_portdrv_pm_ops)

#else /* !PM */

#define PCIE_PORTDRV_PM_OPS NULL
#endif /* !PM */

/*
* pcie_portdrv_probe - Probe PCI-Express port devices
* @dev: PCI-Express port device being probed
*
* If detected invokes the pcie_port_device_register() method for
* this port device.
*
*/
static int pcie_portdrv_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
int type = pci_pcie_type(dev);
int status;

if (!pci_is_pcie(dev) ||
((type != PCI_EXP_TYPE_ROOT_PORT) &&
(type != PCI_EXP_TYPE_UPSTREAM) &&
(type != PCI_EXP_TYPE_DOWNSTREAM) &&
(type != PCI_EXP_TYPE_RC_EC)))
return -ENODEV;

if (type == PCI_EXP_TYPE_RC_EC)
pcie_link_rcec(dev);

status = pcie_port_device_register(dev);
if (status)
return status;

pci_save_state(dev);

dev_pm_set_driver_flags(&dev->dev, DPM_FLAG_NO_DIRECT_COMPLETE |
DPM_FLAG_SMART_SUSPEND);

if (pci_bridge_d3_possible(dev)) {
/*
* Keep the port resumed 100ms to make sure things like
* config space accesses from userspace (lspci) will not
* cause the port to repeatedly suspend and resume.
*/
pm_runtime_set_autosuspend_delay(&dev->dev, 100);
pm_runtime_use_autosuspend(&dev->dev);
pm_runtime_mark_last_busy(&dev->dev);
pm_runtime_put_autosuspend(&dev->dev);
pm_runtime_allow(&dev->dev);
}

return 0;
}

static void pcie_portdrv_remove(struct pci_dev *dev)
{
if (pci_bridge_d3_possible(dev)) {
pm_runtime_forbid(&dev->dev);
pm_runtime_get_noresume(&dev->dev);
pm_runtime_dont_use_autosuspend(&dev->dev);
}

pcie_port_device_remove(dev);
}

static pci_ers_result_t pcie_portdrv_error_detected(struct pci_dev *dev,
pci_channel_state_t error)
{
if (error == pci_channel_io_frozen)
return PCI_ERS_RESULT_NEED_RESET;
return PCI_ERS_RESULT_CAN_RECOVER;
}

static pci_ers_result_t pcie_portdrv_slot_reset(struct pci_dev *dev)
{
size_t off = offsetof(struct pcie_port_service_driver, slot_reset);
device_for_each_child(&dev->dev, &off, pcie_port_device_iter);

pci_restore_state(dev);
pci_save_state(dev);
return PCI_ERS_RESULT_RECOVERED;
}

static pci_ers_result_t pcie_portdrv_mmio_enabled(struct pci_dev *dev)
{
return PCI_ERS_RESULT_RECOVERED;
}

/*
* LINUX Device Driver Model
*/
static const struct pci_device_id port_pci_ids[] = {
/* handle any PCI-Express port */
{ PCI_DEVICE_CLASS(PCI_CLASS_BRIDGE_PCI_NORMAL, ~0) },
/* subtractive decode PCI-to-PCI bridge, class type is 060401h */
{ PCI_DEVICE_CLASS(PCI_CLASS_BRIDGE_PCI_SUBTRACTIVE, ~0) },
/* handle any Root Complex Event Collector */
{ PCI_DEVICE_CLASS(((PCI_CLASS_SYSTEM_RCEC << 8) | 0x00), ~0) },
{ },
};

static const struct pci_error_handlers pcie_portdrv_err_handler = {
.error_detected = pcie_portdrv_error_detected,
.slot_reset = pcie_portdrv_slot_reset,
.mmio_enabled = pcie_portdrv_mmio_enabled,
};

static struct pci_driver pcie_portdriver = {
.name = "pcieport",
.id_table = &port_pci_ids[0],

.probe = pcie_portdrv_probe,
.remove = pcie_portdrv_remove,
.shutdown = pcie_portdrv_remove,

.err_handler = &pcie_portdrv_err_handler,

.driver_managed_dma = true,

.driver.pm = PCIE_PORTDRV_PM_OPS,
};

static int __init dmi_pcie_pme_disable_msi(const struct dmi_system_id *d)
{
pr_notice("%s detected: will not use MSI for PCIe PME signaling\n",
d->ident);
pcie_pme_disable_msi();
return 0;
}

static const struct dmi_system_id pcie_portdrv_dmi_table[] __initconst = {
/*
* Boxes that should not use MSI for PCIe PME signaling.
*/
{
.callback = dmi_pcie_pme_disable_msi,
.ident = "MSI Wind U-100",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR,
"MICRO-STAR INTERNATIONAL CO., LTD"),
DMI_MATCH(DMI_PRODUCT_NAME, "U-100"),
},
},
{}
};

static void __init pcie_init_services(void)
{
pcie_aer_init();
pcie_pme_init();
pcie_dpc_init();
pcie_hp_init();
}

static int __init pcie_portdrv_init(void)
{
if (pcie_ports_disabled)
return -EACCES;

pcie_init_services();
dmi_check_system(pcie_portdrv_dmi_table);

return pci_register_driver(&pcie_portdriver);
}
device_initcall(pcie_portdrv_init);
19 changes: 0 additions & 19 deletions drivers/pci/pcie/portdrv.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,26 +98,7 @@ struct pcie_port_service_driver {
int pcie_port_service_register(struct pcie_port_service_driver *new);
void pcie_port_service_unregister(struct pcie_port_service_driver *new);

/*
* The PCIe Capability Interrupt Message Number (PCIe r3.1, sec 7.8.2) must
* be one of the first 32 MSI-X entries. Per PCI r3.0, sec 6.8.3.1, MSI
* supports a maximum of 32 vectors per function.
*/
#define PCIE_PORT_MAX_MSI_ENTRIES 32

#define get_descriptor_id(type, service) (((type - 4) << 8) | service)

extern struct bus_type pcie_port_bus_type;
int pcie_port_device_register(struct pci_dev *dev);
int pcie_port_device_iter(struct device *dev, void *data);
#ifdef CONFIG_PM
int pcie_port_device_suspend(struct device *dev);
int pcie_port_device_resume_noirq(struct device *dev);
int pcie_port_device_resume(struct device *dev);
int pcie_port_device_runtime_suspend(struct device *dev);
int pcie_port_device_runtime_resume(struct device *dev);
#endif
void pcie_port_device_remove(struct pci_dev *dev);

struct pci_dev;

Expand Down
Loading

0 comments on commit 9303050

Please sign in to comment.