Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+148
View File
@@ -0,0 +1,148 @@
# SPDX-License-Identifier: GPL-2.0
#
# PCI Express Port Bus Configuration
#
config PCIEPORTBUS
bool "PCI Express Port Bus support"
default y if USB4
help
This enables PCI Express Port Bus support. Users can then enable
support for Native Hot-Plug, Advanced Error Reporting, Power
Management Events, and Downstream Port Containment.
#
# Include service Kconfig here
#
config HOTPLUG_PCI_PCIE
bool "PCI Express Hotplug driver"
depends on HOTPLUG_PCI && PCIEPORTBUS
default y if USB4
help
Say Y here if you have a motherboard that supports PCIe native
hotplug.
Thunderbolt/USB4 PCIe tunneling depends on native PCIe hotplug.
When in doubt, say N.
config PCIEAER
bool "PCI Express Advanced Error Reporting support"
depends on PCIEPORTBUS
select RAS
help
This enables PCI Express Root Port Advanced Error Reporting
(AER) driver support. Error reporting messages sent to Root
Port will be handled by PCI Express AER driver.
config PCIEAER_INJECT
tristate "PCI Express error injection support"
depends on PCIEAER
select GENERIC_IRQ_INJECTION
help
This enables PCI Express Root Port Advanced Error Reporting
(AER) software error injector.
Debugging AER code is quite difficult because it is hard
to trigger various real hardware errors. Software-based
error injection can fake almost all kinds of errors with the
help of a user space helper tool aer-inject, which can be
gotten from:
https://github.com/intel/aer-inject.git
#
# PCI Express ECRC
#
config PCIE_ECRC
bool "PCI Express ECRC settings control"
depends on PCIEAER
help
Used to override firmware/bios settings for PCI Express ECRC
(transaction layer end-to-end CRC checking).
When in doubt, say N.
#
# PCI Express ASPM
#
config PCIEASPM
bool "PCI Express ASPM control" if EXPERT
default y
help
This enables OS control over PCI Express ASPM (Active State
Power Management) and Clock Power Management. ASPM supports
state L0/L0s/L1.
ASPM is initially set up by the firmware. With this option enabled,
Linux can modify this state in order to disable ASPM on known-bad
hardware or configurations and enable it when known-safe.
ASPM can be disabled or enabled at runtime via
/sys/module/pcie_aspm/parameters/policy
When in doubt, say Y.
choice
prompt "Default ASPM policy"
default PCIEASPM_DEFAULT
depends on PCIEASPM
config PCIEASPM_DEFAULT
bool "BIOS default"
depends on PCIEASPM
help
Use the BIOS defaults for PCI Express ASPM.
config PCIEASPM_POWERSAVE
bool "Powersave"
depends on PCIEASPM
help
Enable PCI Express ASPM L0s and L1 where possible, even if the
BIOS did not.
config PCIEASPM_POWER_SUPERSAVE
bool "Power Supersave"
depends on PCIEASPM
help
Same as PCIEASPM_POWERSAVE, except it also enables L1 substates where
possible. This would result in higher power savings while staying in L1
where the components support it.
config PCIEASPM_PERFORMANCE
bool "Performance"
depends on PCIEASPM
help
Disable PCI Express ASPM L0s and L1, even if the BIOS enabled them.
endchoice
config PCIE_PME
def_bool y
depends on PCIEPORTBUS && PM
config PCIE_DPC
bool "PCI Express Downstream Port Containment support"
depends on PCIEPORTBUS && PCIEAER
help
This enables PCI Express Downstream Port Containment (DPC)
driver support. DPC events from Root and Downstream ports
will be handled by the DPC driver. If your system doesn't
have this capability or you do not want to use this feature,
it is safe to answer N.
config PCIE_PTM
bool "PCI Express Precision Time Measurement support"
help
This enables PCI Express Precision Time Measurement (PTM)
support.
This is only useful if you have devices that support PTM, but it
is safe to enable even if you don't.
config PCIE_EDR
bool "PCI Express Error Disconnect Recover support"
depends on PCIE_DPC && ACPI
help
This option adds Error Disconnect Recover support as specified
in the Downstream Port Containment Related Enhancements ECN to
the PCI Firmware Specification r3.2. Enable this if you want to
support hybrid DPC model which uses both firmware and OS to
implement DPC.
+16
View File
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for PCI Express features and port driver
pcieportdrv-y := portdrv.o rcec.o
obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o bwctrl.o
obj-y += aspm.o
obj-$(CONFIG_PCIEAER) += aer.o err.o tlp.o
obj-$(CONFIG_CXL_RAS) += aer_cxl_rch.o
obj-$(CONFIG_PCIEAER_INJECT) += aer_inject.o
obj-$(CONFIG_PCIE_PME) += pme.o
obj-$(CONFIG_PCIE_DPC) += dpc.o
obj-$(CONFIG_PCIE_PTM) += ptm.o
obj-$(CONFIG_PCIE_EDR) += edr.o
File diff suppressed because it is too large Load Diff
+104
View File
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2023 AMD Corporation. All rights reserved. */
#include <linux/pci.h>
#include <linux/aer.h>
#include <linux/bitfield.h>
#include "../pci.h"
#include "portdrv.h"
static bool is_cxl_mem_dev(struct pci_dev *dev)
{
/*
* The capability, status, and control fields in Device 0,
* Function 0 DVSEC control the CXL functionality of the
* entire device (CXL 3.0, 8.1.3).
*/
if (dev->devfn != PCI_DEVFN(0, 0))
return false;
/*
* CXL Memory Devices must have the 502h class code set (CXL
* 3.0, 8.1.12.1).
*/
if ((dev->class >> 8) != PCI_CLASS_MEMORY_CXL)
return false;
return true;
}
static bool cxl_error_is_native(struct pci_dev *dev)
{
struct pci_host_bridge *host = pci_find_host_bridge(dev->bus);
return (pcie_ports_native || host->native_aer);
}
static int cxl_rch_handle_error_iter(struct pci_dev *dev, void *data)
{
struct aer_err_info *info = (struct aer_err_info *)data;
const struct pci_error_handlers *err_handler;
if (!is_cxl_mem_dev(dev) || !cxl_error_is_native(dev))
return 0;
guard(device)(&dev->dev);
err_handler = dev->driver ? dev->driver->err_handler : NULL;
if (!err_handler)
return 0;
if (info->severity == AER_CORRECTABLE) {
if (err_handler->cor_error_detected)
err_handler->cor_error_detected(dev);
} else if (err_handler->error_detected) {
if (info->severity == AER_NONFATAL)
err_handler->error_detected(dev, pci_channel_io_normal);
else if (info->severity == AER_FATAL)
err_handler->error_detected(dev, pci_channel_io_frozen);
}
return 0;
}
void cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info)
{
/*
* Internal errors of an RCEC indicate an AER error in an
* RCH's downstream port. Check and handle them in the CXL.mem
* device driver.
*/
if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_EC &&
is_aer_internal_error(info))
pcie_walk_rcec(dev, cxl_rch_handle_error_iter, info);
}
static int handles_cxl_error_iter(struct pci_dev *dev, void *data)
{
bool *handles_cxl = data;
if (!*handles_cxl)
*handles_cxl = is_cxl_mem_dev(dev) && cxl_error_is_native(dev);
/* Non-zero terminates iteration */
return *handles_cxl;
}
static bool handles_cxl_errors(struct pci_dev *rcec)
{
bool handles_cxl = false;
if (pci_pcie_type(rcec) == PCI_EXP_TYPE_RC_EC &&
pcie_aer_is_native(rcec))
pcie_walk_rcec(rcec, handles_cxl_error_iter, &handles_cxl);
return handles_cxl;
}
void cxl_rch_enable_rcec(struct pci_dev *rcec)
{
if (!handles_cxl_errors(rcec))
return;
pci_aer_unmask_internal_errors(rcec);
pci_info(rcec, "CXL: Internal errors unmasked");
}
+548
View File
@@ -0,0 +1,548 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCIe AER software error injection support.
*
* Debugging PCIe AER code is quite difficult because it is hard to
* trigger various real hardware errors. Software based error
* injection can fake almost all kinds of errors with the help of a
* user space helper tool aer-inject, which can be gotten from:
* https://github.com/intel/aer-inject.git
*
* Copyright 2009 Intel Corporation.
* Huang Ying <ying.huang@intel.com>
*/
#define dev_fmt(fmt) "aer_inject: " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/stddef.h>
#include <linux/device.h>
#include "portdrv.h"
/* Override the existing corrected and uncorrected error masks */
static bool aer_mask_override;
module_param(aer_mask_override, bool, 0);
struct aer_error_inj {
u8 bus;
u8 dev;
u8 fn;
u32 uncor_status;
u32 cor_status;
u32 header_log0;
u32 header_log1;
u32 header_log2;
u32 header_log3;
u32 domain;
};
struct aer_error {
struct list_head list;
u32 domain;
unsigned int bus;
unsigned int devfn;
int pos_cap_err;
u32 uncor_status;
u32 cor_status;
u32 header_log0;
u32 header_log1;
u32 header_log2;
u32 header_log3;
u32 root_status;
u32 source_id;
};
struct pci_bus_ops {
struct list_head list;
struct pci_bus *bus;
struct pci_ops *ops;
};
static LIST_HEAD(einjected);
static LIST_HEAD(pci_bus_ops_list);
/* Protect einjected and pci_bus_ops_list */
static DEFINE_SPINLOCK(inject_lock);
static void aer_error_init(struct aer_error *err, u32 domain,
unsigned int bus, unsigned int devfn,
int pos_cap_err)
{
INIT_LIST_HEAD(&err->list);
err->domain = domain;
err->bus = bus;
err->devfn = devfn;
err->pos_cap_err = pos_cap_err;
}
/* inject_lock must be held before calling */
static struct aer_error *__find_aer_error(u32 domain, unsigned int bus,
unsigned int devfn)
{
struct aer_error *err;
list_for_each_entry(err, &einjected, list) {
if (domain == err->domain &&
bus == err->bus &&
devfn == err->devfn)
return err;
}
return NULL;
}
/* inject_lock must be held before calling */
static struct aer_error *__find_aer_error_by_dev(struct pci_dev *dev)
{
int domain = pci_domain_nr(dev->bus);
if (domain < 0)
return NULL;
return __find_aer_error(domain, dev->bus->number, dev->devfn);
}
/* inject_lock must be held before calling */
static struct pci_ops *__find_pci_bus_ops(struct pci_bus *bus)
{
struct pci_bus_ops *bus_ops;
list_for_each_entry(bus_ops, &pci_bus_ops_list, list) {
if (bus_ops->bus == bus)
return bus_ops->ops;
}
return NULL;
}
static struct pci_bus_ops *pci_bus_ops_pop(void)
{
unsigned long flags;
struct pci_bus_ops *bus_ops;
spin_lock_irqsave(&inject_lock, flags);
bus_ops = list_first_entry_or_null(&pci_bus_ops_list,
struct pci_bus_ops, list);
if (bus_ops)
list_del(&bus_ops->list);
spin_unlock_irqrestore(&inject_lock, flags);
return bus_ops;
}
static u32 *find_pci_config_dword(struct aer_error *err, int where,
int *prw1cs)
{
int rw1cs = 0;
u32 *target = NULL;
if (err->pos_cap_err == -1)
return NULL;
switch (where - err->pos_cap_err) {
case PCI_ERR_UNCOR_STATUS:
target = &err->uncor_status;
rw1cs = 1;
break;
case PCI_ERR_COR_STATUS:
target = &err->cor_status;
rw1cs = 1;
break;
case PCI_ERR_HEADER_LOG:
target = &err->header_log0;
break;
case PCI_ERR_HEADER_LOG+4:
target = &err->header_log1;
break;
case PCI_ERR_HEADER_LOG+8:
target = &err->header_log2;
break;
case PCI_ERR_HEADER_LOG+12:
target = &err->header_log3;
break;
case PCI_ERR_ROOT_STATUS:
target = &err->root_status;
rw1cs = 1;
break;
case PCI_ERR_ROOT_ERR_SRC:
target = &err->source_id;
break;
}
if (prw1cs)
*prw1cs = rw1cs;
return target;
}
static int aer_inj_read(struct pci_bus *bus, unsigned int devfn, int where,
int size, u32 *val)
{
struct pci_ops *ops, *my_ops;
int rv;
ops = __find_pci_bus_ops(bus);
if (!ops)
return -1;
my_ops = bus->ops;
bus->ops = ops;
rv = ops->read(bus, devfn, where, size, val);
bus->ops = my_ops;
return rv;
}
static int aer_inj_write(struct pci_bus *bus, unsigned int devfn, int where,
int size, u32 val)
{
struct pci_ops *ops, *my_ops;
int rv;
ops = __find_pci_bus_ops(bus);
if (!ops)
return -1;
my_ops = bus->ops;
bus->ops = ops;
rv = ops->write(bus, devfn, where, size, val);
bus->ops = my_ops;
return rv;
}
static int aer_inj_read_config(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 *val)
{
u32 *sim;
struct aer_error *err;
unsigned long flags;
int domain;
int rv;
spin_lock_irqsave(&inject_lock, flags);
if (size != sizeof(u32))
goto out;
domain = pci_domain_nr(bus);
if (domain < 0)
goto out;
err = __find_aer_error(domain, bus->number, devfn);
if (!err)
goto out;
sim = find_pci_config_dword(err, where, NULL);
if (sim) {
*val = *sim;
spin_unlock_irqrestore(&inject_lock, flags);
return 0;
}
out:
rv = aer_inj_read(bus, devfn, where, size, val);
spin_unlock_irqrestore(&inject_lock, flags);
return rv;
}
static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 val)
{
u32 *sim;
struct aer_error *err;
unsigned long flags;
int rw1cs;
int domain;
int rv;
spin_lock_irqsave(&inject_lock, flags);
if (size != sizeof(u32))
goto out;
domain = pci_domain_nr(bus);
if (domain < 0)
goto out;
err = __find_aer_error(domain, bus->number, devfn);
if (!err)
goto out;
sim = find_pci_config_dword(err, where, &rw1cs);
if (sim) {
if (rw1cs)
*sim ^= val;
else
*sim = val;
spin_unlock_irqrestore(&inject_lock, flags);
return 0;
}
out:
rv = aer_inj_write(bus, devfn, where, size, val);
spin_unlock_irqrestore(&inject_lock, flags);
return rv;
}
static struct pci_ops aer_inj_pci_ops = {
.read = aer_inj_read_config,
.write = aer_inj_write_config,
};
static void pci_bus_ops_init(struct pci_bus_ops *bus_ops,
struct pci_bus *bus,
struct pci_ops *ops)
{
INIT_LIST_HEAD(&bus_ops->list);
bus_ops->bus = bus;
bus_ops->ops = ops;
}
static int pci_bus_set_aer_ops(struct pci_bus *bus)
{
struct pci_ops *ops;
struct pci_bus_ops *bus_ops;
unsigned long flags;
bus_ops = kmalloc_obj(*bus_ops);
if (!bus_ops)
return -ENOMEM;
ops = pci_bus_set_ops(bus, &aer_inj_pci_ops);
spin_lock_irqsave(&inject_lock, flags);
if (ops == &aer_inj_pci_ops)
goto out;
pci_bus_ops_init(bus_ops, bus, ops);
list_add(&bus_ops->list, &pci_bus_ops_list);
bus_ops = NULL;
out:
spin_unlock_irqrestore(&inject_lock, flags);
kfree(bus_ops);
return 0;
}
static int aer_inject(struct aer_error_inj *einj)
{
struct aer_error *err, *rperr;
struct aer_error *err_alloc = NULL, *rperr_alloc = NULL;
struct pci_dev *dev, *rpdev;
struct pcie_device *edev;
struct device *device;
unsigned long flags;
unsigned int devfn = PCI_DEVFN(einj->dev, einj->fn);
int pos_cap_err, rp_pos_cap_err;
u32 sever, cor_mask, uncor_mask, cor_mask_orig = 0, uncor_mask_orig = 0;
int ret = 0;
dev = pci_get_domain_bus_and_slot(einj->domain, einj->bus, devfn);
if (!dev)
return -ENODEV;
rpdev = pcie_find_root_port(dev);
/* If Root Port not found, try to find an RCEC */
if (!rpdev)
rpdev = dev->rcec;
if (!rpdev) {
pci_err(dev, "Neither Root Port nor RCEC found\n");
ret = -ENODEV;
goto out_put;
}
pos_cap_err = dev->aer_cap;
if (!pos_cap_err) {
pci_err(dev, "Device doesn't support AER\n");
ret = -EPROTONOSUPPORT;
goto out_put;
}
pci_read_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_SEVER, &sever);
pci_read_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK, &cor_mask);
pci_read_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK,
&uncor_mask);
rp_pos_cap_err = rpdev->aer_cap;
if (!rp_pos_cap_err) {
pci_err(rpdev, "Root port doesn't support AER\n");
ret = -EPROTONOSUPPORT;
goto out_put;
}
err_alloc = kzalloc_obj(struct aer_error);
if (!err_alloc) {
ret = -ENOMEM;
goto out_put;
}
rperr_alloc = kzalloc_obj(struct aer_error);
if (!rperr_alloc) {
ret = -ENOMEM;
goto out_put;
}
if (aer_mask_override) {
cor_mask_orig = cor_mask;
cor_mask &= !(einj->cor_status);
pci_write_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK,
cor_mask);
uncor_mask_orig = uncor_mask;
uncor_mask &= !(einj->uncor_status);
pci_write_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK,
uncor_mask);
}
spin_lock_irqsave(&inject_lock, flags);
err = __find_aer_error_by_dev(dev);
if (!err) {
err = err_alloc;
err_alloc = NULL;
aer_error_init(err, einj->domain, einj->bus, devfn,
pos_cap_err);
list_add(&err->list, &einjected);
}
err->uncor_status |= einj->uncor_status;
err->cor_status |= einj->cor_status;
err->header_log0 = einj->header_log0;
err->header_log1 = einj->header_log1;
err->header_log2 = einj->header_log2;
err->header_log3 = einj->header_log3;
if (!aer_mask_override && einj->cor_status &&
!(einj->cor_status & ~cor_mask)) {
ret = -EINVAL;
pci_warn(dev, "The correctable error(s) is masked by device\n");
spin_unlock_irqrestore(&inject_lock, flags);
goto out_put;
}
if (!aer_mask_override && einj->uncor_status &&
!(einj->uncor_status & ~uncor_mask)) {
ret = -EINVAL;
pci_warn(dev, "The uncorrectable error(s) is masked by device\n");
spin_unlock_irqrestore(&inject_lock, flags);
goto out_put;
}
rperr = __find_aer_error_by_dev(rpdev);
if (!rperr) {
rperr = rperr_alloc;
rperr_alloc = NULL;
aer_error_init(rperr, pci_domain_nr(rpdev->bus),
rpdev->bus->number, rpdev->devfn,
rp_pos_cap_err);
list_add(&rperr->list, &einjected);
}
if (einj->cor_status) {
if (rperr->root_status & PCI_ERR_ROOT_COR_RCV)
rperr->root_status |= PCI_ERR_ROOT_MULTI_COR_RCV;
else
rperr->root_status |= PCI_ERR_ROOT_COR_RCV;
rperr->source_id &= 0xffff0000;
rperr->source_id |= PCI_DEVID(einj->bus, devfn);
}
if (einj->uncor_status) {
if (rperr->root_status & PCI_ERR_ROOT_UNCOR_RCV)
rperr->root_status |= PCI_ERR_ROOT_MULTI_UNCOR_RCV;
if (sever & einj->uncor_status) {
rperr->root_status |= PCI_ERR_ROOT_FATAL_RCV;
if (!(rperr->root_status & PCI_ERR_ROOT_UNCOR_RCV))
rperr->root_status |= PCI_ERR_ROOT_FIRST_FATAL;
} else
rperr->root_status |= PCI_ERR_ROOT_NONFATAL_RCV;
rperr->root_status |= PCI_ERR_ROOT_UNCOR_RCV;
rperr->source_id &= 0x0000ffff;
rperr->source_id |= PCI_DEVID(einj->bus, devfn) << 16;
}
spin_unlock_irqrestore(&inject_lock, flags);
if (aer_mask_override) {
pci_write_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK,
cor_mask_orig);
pci_write_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK,
uncor_mask_orig);
}
ret = pci_bus_set_aer_ops(dev->bus);
if (ret)
goto out_put;
ret = pci_bus_set_aer_ops(rpdev->bus);
if (ret)
goto out_put;
device = pcie_port_find_device(rpdev, PCIE_PORT_SERVICE_AER);
if (device) {
edev = to_pcie_device(device);
if (!get_service_data(edev)) {
pci_warn(edev->port, "AER service is not initialized\n");
ret = -EPROTONOSUPPORT;
goto out_put;
}
pci_info(edev->port, "Injecting errors %08x/%08x into device %s\n",
einj->cor_status, einj->uncor_status, pci_name(dev));
ret = irq_inject_interrupt(edev->irq);
} else {
pci_err(rpdev, "AER device not found\n");
ret = -ENODEV;
}
out_put:
kfree(err_alloc);
kfree(rperr_alloc);
pci_dev_put(dev);
return ret;
}
static ssize_t aer_inject_write(struct file *filp, const char __user *ubuf,
size_t usize, loff_t *off)
{
struct aer_error_inj einj;
int ret;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (usize < offsetof(struct aer_error_inj, domain) ||
usize > sizeof(einj))
return -EINVAL;
memset(&einj, 0, sizeof(einj));
if (copy_from_user(&einj, ubuf, usize))
return -EFAULT;
ret = aer_inject(&einj);
return ret ? ret : usize;
}
static const struct file_operations aer_inject_fops = {
.write = aer_inject_write,
.owner = THIS_MODULE,
.llseek = noop_llseek,
};
static struct miscdevice aer_inject_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "aer_inject",
.fops = &aer_inject_fops,
};
static int __init aer_inject_init(void)
{
return misc_register(&aer_inject_device);
}
static void __exit aer_inject_exit(void)
{
struct aer_error *err, *err_next;
unsigned long flags;
struct pci_bus_ops *bus_ops;
misc_deregister(&aer_inject_device);
while ((bus_ops = pci_bus_ops_pop())) {
pci_bus_set_ops(bus_ops->bus, bus_ops->ops);
kfree(bus_ops);
}
spin_lock_irqsave(&inject_lock, flags);
list_for_each_entry_safe(err, err_next, &einjected, list) {
list_del(&err->list);
kfree(err);
}
spin_unlock_irqrestore(&inject_lock, flags);
}
module_init(aer_inject_init);
module_exit(aer_inject_exit);
MODULE_DESCRIPTION("PCIe AER software error injector");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
+332
View File
@@ -0,0 +1,332 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* PCIe bandwidth controller
*
* Author: Alexandru Gagniuc <mr.nuke.me@gmail.com>
*
* Copyright (C) 2019 Dell Inc
* Copyright (C) 2023-2024 Intel Corporation
*
* The PCIe bandwidth controller provides a way to alter PCIe Link Speeds
* and notify the operating system when the Link Width or Speed changes. The
* notification capability is required for all Root Ports and Downstream
* Ports supporting Link Width wider than x1 and/or multiple Link Speeds.
*
* This service port driver hooks into the Bandwidth Notification interrupt
* watching for changes or links becoming degraded in operation. It updates
* the cached Current Link Speed that is exposed to user space through sysfs.
*/
#define dev_fmt(fmt) "bwctrl: " fmt
#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pci-bwctrl.h>
#include <linux/rwsem.h>
#include <linux/slab.h>
#include <linux/types.h>
#include "../pci.h"
#include "portdrv.h"
/**
* struct pcie_bwctrl_data - PCIe bandwidth controller
* @set_speed_mutex: Serializes link speed changes
* @cdev: Thermal cooling device associated with the port
*/
struct pcie_bwctrl_data {
struct mutex set_speed_mutex;
struct thermal_cooling_device *cdev;
};
/* Prevent port removal during Link Speed changes. */
static DECLARE_RWSEM(pcie_bwctrl_setspeed_rwsem);
static bool pcie_valid_speed(enum pci_bus_speed speed)
{
return (speed >= PCIE_SPEED_2_5GT) && (speed <= PCIE_SPEED_64_0GT);
}
static u16 pci_bus_speed2lnkctl2(enum pci_bus_speed speed)
{
static const u8 speed_conv[] = {
[PCIE_SPEED_2_5GT] = PCI_EXP_LNKCTL2_TLS_2_5GT,
[PCIE_SPEED_5_0GT] = PCI_EXP_LNKCTL2_TLS_5_0GT,
[PCIE_SPEED_8_0GT] = PCI_EXP_LNKCTL2_TLS_8_0GT,
[PCIE_SPEED_16_0GT] = PCI_EXP_LNKCTL2_TLS_16_0GT,
[PCIE_SPEED_32_0GT] = PCI_EXP_LNKCTL2_TLS_32_0GT,
[PCIE_SPEED_64_0GT] = PCI_EXP_LNKCTL2_TLS_64_0GT,
};
if (WARN_ON_ONCE(!pcie_valid_speed(speed)))
return 0;
return speed_conv[speed];
}
static inline u16 pcie_supported_speeds2target_speed(u8 supported_speeds)
{
return __fls(supported_speeds);
}
/**
* pcie_bwctrl_select_speed - Select Target Link Speed
* @port: PCIe Port
* @speed_req: Requested PCIe Link Speed
*
* Select Target Link Speed by take into account Supported Link Speeds of
* both the Root Port and the Endpoint.
*
* Return: Target Link Speed (1=2.5GT/s, 2=5GT/s, 3=8GT/s, etc.)
*/
static u16 pcie_bwctrl_select_speed(struct pci_dev *port, enum pci_bus_speed speed_req)
{
struct pci_bus *bus = port->subordinate;
u8 desired_speeds, supported_speeds;
struct pci_dev *dev;
desired_speeds = GENMASK(pci_bus_speed2lnkctl2(speed_req),
__fls(PCI_EXP_LNKCAP2_SLS_2_5GB));
supported_speeds = port->supported_speeds;
if (bus) {
down_read(&pci_bus_sem);
dev = list_first_entry_or_null(&bus->devices, struct pci_dev, bus_list);
if (dev)
supported_speeds &= dev->supported_speeds;
up_read(&pci_bus_sem);
}
if (!supported_speeds)
supported_speeds = PCI_EXP_LNKCAP2_SLS_2_5GB;
return pcie_supported_speeds2target_speed(supported_speeds & desired_speeds);
}
static int pcie_bwctrl_change_speed(struct pci_dev *port, u16 target_speed, bool use_lt)
{
int ret;
ret = pcie_capability_clear_and_set_word(port, PCI_EXP_LNKCTL2,
PCI_EXP_LNKCTL2_TLS, target_speed);
if (ret != PCIBIOS_SUCCESSFUL)
return pcibios_err_to_errno(ret);
return pcie_retrain_link(port, use_lt);
}
/**
* pcie_set_target_speed - Set downstream Link Speed for PCIe Port
* @port: PCIe Port
* @speed_req: Requested PCIe Link Speed
* @use_lt: Wait for the LT or DLLLA bit to detect the end of link training
*
* Attempt to set PCIe Port Link Speed to @speed_req. @speed_req may be
* adjusted downwards to the best speed supported by both the Port and PCIe
* Device underneath it.
*
* Return:
* * 0 - on success
* * -EINVAL - @speed_req is not a PCIe Link Speed
* * -ENODEV - @port is not controllable
* * -ETIMEDOUT - changing Link Speed took too long
* * -EAGAIN - Link Speed was changed but @speed_req was not achieved
*/
int pcie_set_target_speed(struct pci_dev *port, enum pci_bus_speed speed_req,
bool use_lt)
{
struct pci_bus *bus = port->subordinate;
u16 target_speed;
int ret;
if (WARN_ON_ONCE(!pcie_valid_speed(speed_req)))
return -EINVAL;
if (bus && bus->cur_bus_speed == speed_req)
return 0;
target_speed = pcie_bwctrl_select_speed(port, speed_req);
scoped_guard(rwsem_read, &pcie_bwctrl_setspeed_rwsem) {
struct pcie_bwctrl_data *data = port->link_bwctrl;
/*
* port->link_bwctrl is NULL during initial scan when called
* e.g. from the Target Speed quirk.
*/
if (data)
mutex_lock(&data->set_speed_mutex);
ret = pcie_bwctrl_change_speed(port, target_speed, use_lt);
if (data)
mutex_unlock(&data->set_speed_mutex);
}
/*
* Despite setting higher speed into the Target Link Speed, empty
* bus won't train to 5GT+ speeds.
*/
if (!ret && bus && bus->cur_bus_speed != speed_req &&
!list_empty(&bus->devices))
ret = -EAGAIN;
return ret;
}
static void pcie_bwnotif_enable(struct pcie_device *srv)
{
struct pci_dev *port = srv->port;
u16 link_status;
int ret;
/* Note if LBMS has been seen so far */
ret = pcie_capability_read_word(port, PCI_EXP_LNKSTA, &link_status);
if (ret == PCIBIOS_SUCCESSFUL && link_status & PCI_EXP_LNKSTA_LBMS)
set_bit(PCI_LINK_LBMS_SEEN, &port->priv_flags);
pcie_capability_set_word(port, PCI_EXP_LNKCTL,
PCI_EXP_LNKCTL_LBMIE | PCI_EXP_LNKCTL_LABIE);
pcie_capability_write_word(port, PCI_EXP_LNKSTA,
PCI_EXP_LNKSTA_LBMS | PCI_EXP_LNKSTA_LABS);
/*
* Update after enabling notifications & clearing status bits ensures
* link speed is up to date.
*/
pcie_update_link_speed(port->subordinate, PCIE_BWCTRL_ENABLE);
}
static void pcie_bwnotif_disable(struct pci_dev *port)
{
pcie_capability_clear_word(port, PCI_EXP_LNKCTL,
PCI_EXP_LNKCTL_LBMIE | PCI_EXP_LNKCTL_LABIE);
}
static irqreturn_t pcie_bwnotif_irq(int irq, void *context)
{
struct pcie_device *srv = context;
struct pci_dev *port = srv->port;
u16 link_status, events;
int ret;
ret = pcie_capability_read_word(port, PCI_EXP_LNKSTA, &link_status);
if (ret != PCIBIOS_SUCCESSFUL)
return IRQ_NONE;
events = link_status & (PCI_EXP_LNKSTA_LBMS | PCI_EXP_LNKSTA_LABS);
if (!events)
return IRQ_NONE;
if (events & PCI_EXP_LNKSTA_LBMS)
set_bit(PCI_LINK_LBMS_SEEN, &port->priv_flags);
pcie_capability_write_word(port, PCI_EXP_LNKSTA, events);
/*
* Interrupts will not be triggered from any further Link Speed
* change until LBMS is cleared by the write. Therefore, re-read the
* speed (inside pcie_update_link_speed()) after LBMS has been
* cleared to avoid missing link speed changes.
*/
pcie_update_link_speed(port->subordinate, PCIE_BWCTRL_IRQ);
return IRQ_HANDLED;
}
void pcie_reset_lbms(struct pci_dev *port)
{
clear_bit(PCI_LINK_LBMS_SEEN, &port->priv_flags);
pcie_capability_write_word(port, PCI_EXP_LNKSTA, PCI_EXP_LNKSTA_LBMS);
}
static int pcie_bwnotif_probe(struct pcie_device *srv)
{
struct pci_dev *port = srv->port;
int ret;
if (port->no_bw_notif)
return -ENODEV;
/* Can happen if we run out of bus numbers during enumeration. */
if (!port->subordinate)
return -ENODEV;
struct pcie_bwctrl_data *data = devm_kzalloc(&srv->device,
sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
ret = devm_mutex_init(&srv->device, &data->set_speed_mutex);
if (ret)
return ret;
scoped_guard(rwsem_write, &pcie_bwctrl_setspeed_rwsem) {
port->link_bwctrl = data;
ret = request_irq(srv->irq, pcie_bwnotif_irq,
IRQF_SHARED, "PCIe bwctrl", srv);
if (ret) {
port->link_bwctrl = NULL;
return ret;
}
pcie_bwnotif_enable(srv);
}
pci_dbg(port, "enabled with IRQ %d\n", srv->irq);
/* Don't fail on errors. Don't leave IS_ERR() "pointer" into ->cdev */
port->link_bwctrl->cdev = pcie_cooling_device_register(port);
if (IS_ERR(port->link_bwctrl->cdev))
port->link_bwctrl->cdev = NULL;
return 0;
}
static void pcie_bwnotif_remove(struct pcie_device *srv)
{
struct pcie_bwctrl_data *data = srv->port->link_bwctrl;
pcie_cooling_device_unregister(data->cdev);
scoped_guard(rwsem_write, &pcie_bwctrl_setspeed_rwsem) {
pcie_bwnotif_disable(srv->port);
free_irq(srv->irq, srv);
srv->port->link_bwctrl = NULL;
}
}
static int pcie_bwnotif_suspend(struct pcie_device *srv)
{
pcie_bwnotif_disable(srv->port);
return 0;
}
static int pcie_bwnotif_resume(struct pcie_device *srv)
{
pcie_bwnotif_enable(srv);
return 0;
}
static struct pcie_port_service_driver pcie_bwctrl_driver = {
.name = "pcie_bwctrl",
.port_type = PCIE_ANY_PORT,
.service = PCIE_PORT_SERVICE_BWCTRL,
.probe = pcie_bwnotif_probe,
.suspend = pcie_bwnotif_suspend,
.resume = pcie_bwnotif_resume,
.remove = pcie_bwnotif_remove,
};
int __init pcie_bwctrl_init(void)
{
return pcie_port_service_register(&pcie_bwctrl_driver);
}
+536
View File
@@ -0,0 +1,536 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI Express Downstream Port Containment services driver
* Author: Keith Busch <keith.busch@intel.com>
*
* Copyright (C) 2016 Intel Corp.
*/
#define dev_fmt(fmt) "DPC: " fmt
#include <linux/aer.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/pci.h>
#include "portdrv.h"
#include "../pci.h"
#define PCI_EXP_DPC_CTL_EN_MASK (PCI_EXP_DPC_CTL_EN_FATAL | \
PCI_EXP_DPC_CTL_EN_NONFATAL)
static const char * const rp_pio_error_string[] = {
"Configuration Request received UR Completion", /* Bit Position 0 */
"Configuration Request received CA Completion", /* Bit Position 1 */
"Configuration Request Completion Timeout", /* Bit Position 2 */
NULL,
NULL,
NULL,
NULL,
NULL,
"I/O Request received UR Completion", /* Bit Position 8 */
"I/O Request received CA Completion", /* Bit Position 9 */
"I/O Request Completion Timeout", /* Bit Position 10 */
NULL,
NULL,
NULL,
NULL,
NULL,
"Memory Request received UR Completion", /* Bit Position 16 */
"Memory Request received CA Completion", /* Bit Position 17 */
"Memory Request Completion Timeout", /* Bit Position 18 */
};
void pci_save_dpc_state(struct pci_dev *dev)
{
struct pci_cap_saved_state *save_state;
u16 *cap;
if (!pci_is_pcie(dev))
return;
save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_DPC);
if (!save_state)
return;
cap = (u16 *)&save_state->cap.data[0];
pci_read_config_word(dev, dev->dpc_cap + PCI_EXP_DPC_CTL, cap);
}
void pci_restore_dpc_state(struct pci_dev *dev)
{
struct pci_cap_saved_state *save_state;
u16 *cap;
if (!pci_is_pcie(dev))
return;
save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_DPC);
if (!save_state)
return;
cap = (u16 *)&save_state->cap.data[0];
pci_write_config_word(dev, dev->dpc_cap + PCI_EXP_DPC_CTL, *cap);
}
static DECLARE_WAIT_QUEUE_HEAD(dpc_completed_waitqueue);
#ifdef CONFIG_HOTPLUG_PCI_PCIE
static bool dpc_completed(struct pci_dev *pdev)
{
u16 status;
pci_read_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_STATUS, &status);
if ((!PCI_POSSIBLE_ERROR(status)) && (status & PCI_EXP_DPC_STATUS_TRIGGER))
return false;
if (test_bit(PCI_DPC_RECOVERING, &pdev->priv_flags))
return false;
return true;
}
/**
* pci_dpc_recovered - whether DPC triggered and has recovered successfully
* @pdev: PCI device
*
* Return true if DPC was triggered for @pdev and has recovered successfully.
* Wait for recovery if it hasn't completed yet. Called from the PCIe hotplug
* driver to recognize and ignore Link Down/Up events caused by DPC.
*/
bool pci_dpc_recovered(struct pci_dev *pdev)
{
struct pci_host_bridge *host;
if (!pdev->dpc_cap)
return false;
/*
* Synchronization between hotplug and DPC is not supported
* if DPC is owned by firmware and EDR is not enabled.
*/
host = pci_find_host_bridge(pdev->bus);
if (!host->native_dpc && !IS_ENABLED(CONFIG_PCIE_EDR))
return false;
/*
* Need a timeout in case DPC never completes due to failure of
* dpc_wait_rp_inactive(). The spec doesn't mandate a time limit,
* but reports indicate that DPC completes within 4 seconds.
*/
wait_event_timeout(dpc_completed_waitqueue, dpc_completed(pdev),
msecs_to_jiffies(4000));
return test_and_clear_bit(PCI_DPC_RECOVERED, &pdev->priv_flags);
}
#endif /* CONFIG_HOTPLUG_PCI_PCIE */
static int dpc_wait_rp_inactive(struct pci_dev *pdev)
{
unsigned long timeout = jiffies + HZ;
u16 cap = pdev->dpc_cap, status;
pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &status);
while (status & PCI_EXP_DPC_RP_BUSY &&
!time_after(jiffies, timeout)) {
msleep(10);
pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &status);
}
if (status & PCI_EXP_DPC_RP_BUSY) {
pci_warn(pdev, "root port still busy\n");
return -EBUSY;
}
return 0;
}
pci_ers_result_t dpc_reset_link(struct pci_dev *pdev)
{
pci_ers_result_t ret;
u16 cap;
set_bit(PCI_DPC_RECOVERING, &pdev->priv_flags);
/*
* DPC disables the Link automatically in hardware, so it has
* already been reset by the time we get here.
*/
cap = pdev->dpc_cap;
/*
* Wait until the Link is inactive, then clear DPC Trigger Status
* to allow the Port to leave DPC.
*/
if (!pcie_wait_for_link(pdev, false))
pci_info(pdev, "Data Link Layer Link Active not cleared in 1000 msec\n");
if (pdev->dpc_rp_extensions && dpc_wait_rp_inactive(pdev)) {
clear_bit(PCI_DPC_RECOVERED, &pdev->priv_flags);
ret = PCI_ERS_RESULT_DISCONNECT;
goto out;
}
pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS,
PCI_EXP_DPC_STATUS_TRIGGER);
if (pci_bridge_wait_for_secondary_bus(pdev, "DPC")) {
clear_bit(PCI_DPC_RECOVERED, &pdev->priv_flags);
ret = PCI_ERS_RESULT_DISCONNECT;
} else {
set_bit(PCI_DPC_RECOVERED, &pdev->priv_flags);
ret = PCI_ERS_RESULT_RECOVERED;
}
out:
clear_bit(PCI_DPC_RECOVERING, &pdev->priv_flags);
wake_up_all(&dpc_completed_waitqueue);
return ret;
}
static void dpc_process_rp_pio_error(struct pci_dev *pdev)
{
u16 cap = pdev->dpc_cap, dpc_status, first_error;
u32 status, mask, sev, syserr, exc, log;
struct pcie_tlp_log tlp_log;
int i;
pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_STATUS, &status);
pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_MASK, &mask);
pci_err(pdev, "rp_pio_status: %#010x, rp_pio_mask: %#010x\n",
status, mask);
pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_SEVERITY, &sev);
pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_SYSERROR, &syserr);
pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_EXCEPTION, &exc);
pci_err(pdev, "RP PIO severity=%#010x, syserror=%#010x, exception=%#010x\n",
sev, syserr, exc);
/* Get First Error Pointer */
pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &dpc_status);
first_error = FIELD_GET(PCI_EXP_DPC_RP_PIO_FEP, dpc_status);
for (i = 0; i < ARRAY_SIZE(rp_pio_error_string); i++) {
if ((status & ~mask) & (1 << i))
pci_err(pdev, "[%2d] %s%s\n", i, rp_pio_error_string[i],
first_error == i ? " (First)" : "");
}
if (pdev->dpc_rp_log_size < PCIE_STD_NUM_TLP_HEADERLOG)
goto clear_status;
pcie_read_tlp_log(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG,
cap + PCI_EXP_DPC_RP_PIO_TLPPREFIX_LOG,
dpc_tlp_log_len(pdev),
pdev->subordinate->flit_mode,
&tlp_log);
pcie_print_tlp_log(pdev, &tlp_log, KERN_ERR, dev_fmt(""));
if (pdev->dpc_rp_log_size < PCIE_STD_NUM_TLP_HEADERLOG + 1)
goto clear_status;
pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_IMPSPEC_LOG, &log);
pci_err(pdev, "RP PIO ImpSpec Log %#010x\n", log);
clear_status:
pci_write_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_STATUS, status);
}
static int dpc_get_aer_uncorrect_severity(struct pci_dev *dev,
struct aer_err_info *info)
{
int pos = dev->aer_cap;
u32 status, mask, sev;
pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status);
pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_MASK, &mask);
status &= ~mask;
if (!status)
return 0;
pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &sev);
status &= sev;
if (status)
info->severity = AER_FATAL;
else
info->severity = AER_NONFATAL;
info->level = KERN_ERR;
info->dev[0] = dev;
info->error_dev_num = 1;
info->ratelimit_print[0] = 1;
return 1;
}
void dpc_process_error(struct pci_dev *pdev)
{
u16 cap = pdev->dpc_cap, status, source, reason, ext_reason;
struct aer_err_info info = {};
pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &status);
reason = status & PCI_EXP_DPC_STATUS_TRIGGER_RSN;
switch (reason) {
case PCI_EXP_DPC_STATUS_TRIGGER_RSN_UNCOR:
pci_warn(pdev, "containment event, status:%#06x: unmasked uncorrectable error detected\n",
status);
if (dpc_get_aer_uncorrect_severity(pdev, &info) &&
aer_get_device_error_info(&info, 0)) {
aer_print_error(&info, 0);
pci_aer_clear_nonfatal_status(pdev);
pci_aer_clear_fatal_status(pdev);
}
break;
case PCI_EXP_DPC_STATUS_TRIGGER_RSN_NFE:
case PCI_EXP_DPC_STATUS_TRIGGER_RSN_FE:
pci_read_config_word(pdev, cap + PCI_EXP_DPC_SOURCE_ID,
&source);
pci_warn(pdev, "containment event, status:%#06x, %s received from %04x:%02x:%02x.%d\n",
status,
(reason == PCI_EXP_DPC_STATUS_TRIGGER_RSN_FE) ?
"ERR_FATAL" : "ERR_NONFATAL",
pci_domain_nr(pdev->bus), PCI_BUS_NUM(source),
PCI_SLOT(source), PCI_FUNC(source));
break;
case PCI_EXP_DPC_STATUS_TRIGGER_RSN_IN_EXT:
ext_reason = status & PCI_EXP_DPC_STATUS_TRIGGER_RSN_EXT;
pci_warn(pdev, "containment event, status:%#06x: %s detected\n",
status,
(ext_reason == PCI_EXP_DPC_STATUS_TRIGGER_RSN_RP_PIO) ?
"RP PIO error" :
(ext_reason == PCI_EXP_DPC_STATUS_TRIGGER_RSN_SW_TRIGGER) ?
"software trigger" :
"reserved error");
/* show RP PIO error detail information */
if (ext_reason == PCI_EXP_DPC_STATUS_TRIGGER_RSN_RP_PIO &&
pdev->dpc_rp_extensions)
dpc_process_rp_pio_error(pdev);
break;
}
}
static void pci_clear_surpdn_errors(struct pci_dev *pdev)
{
if (pdev->dpc_rp_extensions)
pci_write_config_dword(pdev, pdev->dpc_cap +
PCI_EXP_DPC_RP_PIO_STATUS, ~0);
/*
* In practice, Surprise Down errors have been observed to also set
* error bits in the Status Register as well as the Fatal Error
* Detected bit in the Device Status Register.
*/
pci_write_config_word(pdev, PCI_STATUS, 0xffff);
pcie_capability_write_word(pdev, PCI_EXP_DEVSTA, PCI_EXP_DEVSTA_FED);
}
static void dpc_handle_surprise_removal(struct pci_dev *pdev)
{
if (!pcie_wait_for_link(pdev, false)) {
pci_info(pdev, "Data Link Layer Link Active not cleared in 1000 msec\n");
goto out;
}
if (pdev->dpc_rp_extensions && dpc_wait_rp_inactive(pdev))
goto out;
pci_aer_raw_clear_status(pdev);
pci_clear_surpdn_errors(pdev);
pci_write_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_STATUS,
PCI_EXP_DPC_STATUS_TRIGGER);
out:
clear_bit(PCI_DPC_RECOVERED, &pdev->priv_flags);
wake_up_all(&dpc_completed_waitqueue);
}
static bool dpc_is_surprise_removal(struct pci_dev *pdev)
{
u16 status;
if (!pdev->is_hotplug_bridge)
return false;
if (pci_read_config_word(pdev, pdev->aer_cap + PCI_ERR_UNCOR_STATUS,
&status))
return false;
return status & PCI_ERR_UNC_SURPDN;
}
static irqreturn_t dpc_handler(int irq, void *context)
{
struct pci_dev *pdev = context;
/*
* According to PCIe r6.0 sec 6.7.6, errors are an expected side effect
* of async removal and should be ignored by software.
*/
if (dpc_is_surprise_removal(pdev)) {
dpc_handle_surprise_removal(pdev);
return IRQ_HANDLED;
}
pci_dev_get(pdev);
dpc_process_error(pdev);
/* We configure DPC so it only triggers on ERR_FATAL */
pcie_do_recovery(pdev, pci_channel_io_frozen, dpc_reset_link);
pci_dev_put(pdev);
return IRQ_HANDLED;
}
static irqreturn_t dpc_irq(int irq, void *context)
{
struct pci_dev *pdev = context;
u16 cap = pdev->dpc_cap, status;
pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &status);
if (!(status & PCI_EXP_DPC_STATUS_INTERRUPT) || PCI_POSSIBLE_ERROR(status))
return IRQ_NONE;
pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS,
PCI_EXP_DPC_STATUS_INTERRUPT);
if (status & PCI_EXP_DPC_STATUS_TRIGGER)
return IRQ_WAKE_THREAD;
return IRQ_HANDLED;
}
void pci_dpc_init(struct pci_dev *pdev)
{
u16 cap;
pdev->dpc_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_DPC);
if (!pdev->dpc_cap)
return;
pci_read_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CAP, &cap);
if (!(cap & PCI_EXP_DPC_CAP_RP_EXT))
return;
pdev->dpc_rp_extensions = true;
/* Quirks may set dpc_rp_log_size if device or firmware is buggy */
if (!pdev->dpc_rp_log_size) {
u16 flags;
int ret;
ret = pcie_capability_read_word(pdev, PCI_EXP_FLAGS, &flags);
if (ret)
return;
pdev->dpc_rp_log_size =
FIELD_GET(PCI_EXP_DPC_RP_PIO_LOG_SIZE, cap);
if (FIELD_GET(PCI_EXP_FLAGS_FLIT, flags))
pdev->dpc_rp_log_size += FIELD_GET(PCI_EXP_DPC_RP_PIO_LOG_SIZE4,
cap) << 4;
if (pdev->dpc_rp_log_size < PCIE_STD_NUM_TLP_HEADERLOG ||
pdev->dpc_rp_log_size > PCIE_STD_MAX_TLP_HEADERLOG + 1) {
pci_err(pdev, "RP PIO log size %u is invalid\n",
pdev->dpc_rp_log_size);
pdev->dpc_rp_log_size = 0;
}
}
}
static void dpc_enable(struct pcie_device *dev)
{
struct pci_dev *pdev = dev->port;
int dpc = pdev->dpc_cap;
u16 ctl;
/*
* Clear DPC Interrupt Status so we don't get an interrupt for an
* old event when setting DPC Interrupt Enable.
*/
pci_write_config_word(pdev, dpc + PCI_EXP_DPC_STATUS,
PCI_EXP_DPC_STATUS_INTERRUPT);
pci_read_config_word(pdev, dpc + PCI_EXP_DPC_CTL, &ctl);
ctl &= ~PCI_EXP_DPC_CTL_EN_MASK;
ctl |= PCI_EXP_DPC_CTL_EN_FATAL | PCI_EXP_DPC_CTL_INT_EN;
pci_write_config_word(pdev, dpc + PCI_EXP_DPC_CTL, ctl);
}
static void dpc_disable(struct pcie_device *dev)
{
struct pci_dev *pdev = dev->port;
int dpc = pdev->dpc_cap;
u16 ctl;
/* Disable DPC triggering and DPC interrupts */
pci_read_config_word(pdev, dpc + PCI_EXP_DPC_CTL, &ctl);
ctl &= ~(PCI_EXP_DPC_CTL_EN_FATAL | PCI_EXP_DPC_CTL_INT_EN);
pci_write_config_word(pdev, dpc + PCI_EXP_DPC_CTL, ctl);
}
#define FLAG(x, y) (((x) & (y)) ? '+' : '-')
static int dpc_probe(struct pcie_device *dev)
{
struct pci_dev *pdev = dev->port;
struct device *device = &dev->device;
int status;
u16 cap;
if (!pcie_aer_is_native(pdev) && !pcie_ports_dpc_native)
return -ENOTSUPP;
status = devm_request_threaded_irq(device, dev->irq, dpc_irq,
dpc_handler, IRQF_SHARED,
"pcie-dpc", pdev);
if (status) {
pci_warn(pdev, "request IRQ%d failed: %d\n", dev->irq,
status);
return status;
}
pci_read_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CAP, &cap);
dpc_enable(dev);
pci_info(pdev, "enabled with IRQ %d\n", dev->irq);
pci_info(pdev, "error containment capabilities: Int Msg #%d, RPExt%c PoisonedTLP%c SwTrigger%c RP PIO Log %d, DL_ActiveErr%c\n",
cap & PCI_EXP_DPC_IRQ, FLAG(cap, PCI_EXP_DPC_CAP_RP_EXT),
FLAG(cap, PCI_EXP_DPC_CAP_POISONED_TLP),
FLAG(cap, PCI_EXP_DPC_CAP_SW_TRIGGER), pdev->dpc_rp_log_size,
FLAG(cap, PCI_EXP_DPC_CAP_DL_ACTIVE));
pci_add_ext_cap_save_buffer(pdev, PCI_EXT_CAP_ID_DPC, sizeof(u16));
return status;
}
static int dpc_suspend(struct pcie_device *dev)
{
dpc_disable(dev);
return 0;
}
static int dpc_resume(struct pcie_device *dev)
{
dpc_enable(dev);
return 0;
}
static void dpc_remove(struct pcie_device *dev)
{
dpc_disable(dev);
}
static struct pcie_port_service_driver dpcdriver = {
.name = "dpc",
.port_type = PCIE_ANY_PORT,
.service = PCIE_PORT_SERVICE_DPC,
.probe = dpc_probe,
.suspend = dpc_suspend,
.resume = dpc_resume,
.remove = dpc_remove,
};
int __init pcie_dpc_init(void)
{
return pcie_port_service_register(&dpcdriver);
}
+255
View File
@@ -0,0 +1,255 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI Error Disconnect Recover support
* Author: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
*
* Copyright (C) 2020 Intel Corp.
*/
#define dev_fmt(fmt) "EDR: " fmt
#include <linux/pci.h>
#include <linux/pci-acpi.h>
#include "portdrv.h"
#include "../pci.h"
#define EDR_PORT_DPC_ENABLE_DSM 0x0C
#define EDR_PORT_LOCATE_DSM 0x0D
#define EDR_OST_SUCCESS 0x80
#define EDR_OST_FAILED 0x81
/*
* _DSM wrapper function to enable/disable DPC
* @pdev : PCI device structure
*
* returns 0 on success or errno on failure.
*/
static int acpi_enable_dpc(struct pci_dev *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
union acpi_object *obj, argv4, req;
int status = 0;
/*
* Per PCI Firmware r3.3, sec 4.6.12, EDR_PORT_DPC_ENABLE_DSM is
* optional. Return success if it's not implemented.
*/
if (!acpi_check_dsm(adev->handle, &pci_acpi_dsm_guid, 6,
1ULL << EDR_PORT_DPC_ENABLE_DSM))
return 0;
req.type = ACPI_TYPE_INTEGER;
req.integer.value = 1;
argv4.type = ACPI_TYPE_PACKAGE;
argv4.package.count = 1;
argv4.package.elements = &req;
obj = acpi_evaluate_dsm(adev->handle, &pci_acpi_dsm_guid, 6,
EDR_PORT_DPC_ENABLE_DSM, &argv4);
if (!obj)
return 0;
if (obj->type != ACPI_TYPE_INTEGER) {
pci_err(pdev, FW_BUG "Enable DPC _DSM returned non integer\n");
status = -EIO;
}
if (obj->integer.value != 1) {
pci_err(pdev, "Enable DPC _DSM failed to enable DPC\n");
status = -EIO;
}
ACPI_FREE(obj);
return status;
}
/*
* _DSM wrapper function to locate DPC port
* @pdev : Device which received EDR event
*
* Returns pci_dev or NULL. Caller is responsible for dropping a reference
* on the returned pci_dev with pci_dev_put().
*/
static struct pci_dev *acpi_dpc_port_get(struct pci_dev *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
union acpi_object *obj;
u16 port;
/*
* If EDR_PORT_LOCATE_DSM is not implemented under the target of
* EDR, the target is the port that experienced the containment
* event (PCI Firmware r3.3, sec 4.6.13).
*/
if (!acpi_check_dsm(adev->handle, &pci_acpi_dsm_guid, 5,
1ULL << EDR_PORT_LOCATE_DSM))
return pci_dev_get(pdev);
obj = acpi_evaluate_dsm(adev->handle, &pci_acpi_dsm_guid, 5,
EDR_PORT_LOCATE_DSM, NULL);
if (!obj)
return pci_dev_get(pdev);
if (obj->type != ACPI_TYPE_INTEGER) {
ACPI_FREE(obj);
pci_err(pdev, FW_BUG "Locate Port _DSM returned non integer\n");
return NULL;
}
/*
* Bit 31 represents the success/failure of the operation. If bit
* 31 is set, the operation failed.
*/
if (obj->integer.value & BIT(31)) {
ACPI_FREE(obj);
pci_err(pdev, "Locate Port _DSM failed\n");
return NULL;
}
/*
* Firmware returns DPC port BDF details in following format:
* 15:8 = bus
* 7:3 = device
* 2:0 = function
*/
port = obj->integer.value;
ACPI_FREE(obj);
return pci_get_domain_bus_and_slot(pci_domain_nr(pdev->bus),
PCI_BUS_NUM(port), port & 0xff);
}
/*
* _OST wrapper function to let firmware know the status of EDR event
* @pdev : Device used to send _OST
* @edev : Device which experienced EDR event
* @status : Status of EDR event
*/
static int acpi_send_edr_status(struct pci_dev *pdev, struct pci_dev *edev,
u16 status)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
u32 ost_status;
pci_dbg(pdev, "Status for %s: %#x\n", pci_name(edev), status);
ost_status = PCI_DEVID(edev->bus->number, edev->devfn) << 16;
ost_status |= status;
status = acpi_evaluate_ost(adev->handle, ACPI_NOTIFY_DISCONNECT_RECOVER,
ost_status, NULL);
if (ACPI_FAILURE(status))
return -EINVAL;
return 0;
}
static void edr_handle_event(acpi_handle handle, u32 event, void *data)
{
struct pci_dev *pdev = data, *edev;
pci_ers_result_t estate = PCI_ERS_RESULT_DISCONNECT;
u16 status;
if (event != ACPI_NOTIFY_DISCONNECT_RECOVER)
return;
/*
* pdev is a Root Port or Downstream Port that is still present and
* has triggered a containment event, e.g., DPC, so its child
* devices have been disconnected (ACPI r6.5, sec 5.6.6).
*/
pci_info(pdev, "EDR event received\n");
/*
* Locate the port that experienced the containment event. pdev
* may be that port or a parent of it (PCI Firmware r3.3, sec
* 4.6.13).
*/
edev = acpi_dpc_port_get(pdev);
if (!edev) {
pci_err(pdev, "Firmware failed to locate DPC port\n");
return;
}
pci_dbg(pdev, "Reported EDR dev: %s\n", pci_name(edev));
/* If port does not support DPC, just send the OST */
if (!edev->dpc_cap) {
pci_err(edev, FW_BUG "This device doesn't support DPC\n");
goto send_ost;
}
/* Check if there is a valid DPC trigger */
pci_read_config_word(edev, edev->dpc_cap + PCI_EXP_DPC_STATUS, &status);
if (!(status & PCI_EXP_DPC_STATUS_TRIGGER)) {
pci_err(edev, "Invalid DPC trigger %#010x\n", status);
goto send_ost;
}
dpc_process_error(edev);
pci_aer_raw_clear_status(edev);
/*
* Irrespective of whether the DPC event is triggered by ERR_FATAL
* or ERR_NONFATAL, since the link is already down, use the FATAL
* error recovery path for both cases.
*/
estate = pcie_do_recovery(edev, pci_channel_io_frozen, dpc_reset_link);
send_ost:
/*
* If recovery is successful, send _OST(0xF, BDF << 16 | 0x80)
* to firmware. If not successful, send _OST(0xF, BDF << 16 | 0x81).
*/
if (estate == PCI_ERS_RESULT_RECOVERED) {
pci_dbg(edev, "DPC port successfully recovered\n");
pcie_clear_device_status(edev);
acpi_send_edr_status(pdev, edev, EDR_OST_SUCCESS);
} else {
pci_dbg(edev, "DPC port recovery failed\n");
acpi_send_edr_status(pdev, edev, EDR_OST_FAILED);
}
pci_dev_put(edev);
}
void pci_acpi_add_edr_notifier(struct pci_dev *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
acpi_status status;
if (!adev) {
pci_dbg(pdev, "No valid ACPI node, skipping EDR init\n");
return;
}
status = acpi_install_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY,
edr_handle_event, pdev);
if (ACPI_FAILURE(status)) {
pci_err(pdev, "Failed to install notify handler\n");
return;
}
if (acpi_enable_dpc(pdev))
acpi_remove_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY,
edr_handle_event);
else
pci_dbg(pdev, "Notify handler installed\n");
}
void pci_acpi_remove_edr_notifier(struct pci_dev *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
if (!adev)
return;
acpi_remove_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY,
edr_handle_event);
pci_dbg(pdev, "Notify handler removed\n");
}
+299
View File
@@ -0,0 +1,299 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This file implements the error recovery as a core part of PCIe error
* reporting. When a PCIe error is delivered, an error message will be
* collected and printed to console, then, an error recovery procedure
* will be executed by following the PCI error recovery rules.
*
* Copyright (C) 2006 Intel Corp.
* Tom Long Nguyen (tom.l.nguyen@intel.com)
* Zhang Yanmin (yanmin.zhang@intel.com)
*/
#define dev_fmt(fmt) "AER: " fmt
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/aer.h>
#include "portdrv.h"
#include "../pci.h"
static pci_ers_result_t merge_result(enum pci_ers_result orig,
enum pci_ers_result new)
{
if (new == PCI_ERS_RESULT_NO_AER_DRIVER)
return PCI_ERS_RESULT_NO_AER_DRIVER;
if (new == PCI_ERS_RESULT_NONE)
return orig;
switch (orig) {
case PCI_ERS_RESULT_CAN_RECOVER:
case PCI_ERS_RESULT_RECOVERED:
orig = new;
break;
case PCI_ERS_RESULT_DISCONNECT:
if (new == PCI_ERS_RESULT_NEED_RESET)
orig = PCI_ERS_RESULT_NEED_RESET;
break;
default:
break;
}
return orig;
}
static int report_error_detected(struct pci_dev *dev,
pci_channel_state_t state,
enum pci_ers_result *result)
{
struct pci_driver *pdrv;
pci_ers_result_t vote;
const struct pci_error_handlers *err_handler;
device_lock(&dev->dev);
pdrv = dev->driver;
if (pci_dev_is_disconnected(dev)) {
vote = PCI_ERS_RESULT_DISCONNECT;
} else if (!pci_dev_set_io_state(dev, state)) {
pci_info(dev, "can't recover (state transition %u -> %u invalid)\n",
dev->error_state, state);
vote = PCI_ERS_RESULT_NONE;
} else if (!pdrv || !pdrv->err_handler ||
!pdrv->err_handler->error_detected) {
/*
* If any device in the subtree does not have an error_detected
* callback, PCI_ERS_RESULT_NO_AER_DRIVER prevents subsequent
* error callbacks of "any" device in the subtree, and will
* exit in the disconnected error state.
*/
if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) {
vote = PCI_ERS_RESULT_NO_AER_DRIVER;
pci_info(dev, "can't recover (no error_detected callback)\n");
} else {
vote = PCI_ERS_RESULT_NONE;
}
} else {
err_handler = pdrv->err_handler;
vote = err_handler->error_detected(dev, state);
}
pci_uevent_ers(dev, vote);
*result = merge_result(*result, vote);
device_unlock(&dev->dev);
return 0;
}
static int pci_pm_runtime_get_sync(struct pci_dev *pdev, void *data)
{
pm_runtime_get_sync(&pdev->dev);
return 0;
}
static int pci_pm_runtime_put(struct pci_dev *pdev, void *data)
{
pm_runtime_put(&pdev->dev);
return 0;
}
static int report_frozen_detected(struct pci_dev *dev, void *data)
{
return report_error_detected(dev, pci_channel_io_frozen, data);
}
static int report_normal_detected(struct pci_dev *dev, void *data)
{
return report_error_detected(dev, pci_channel_io_normal, data);
}
static int report_perm_failure_detected(struct pci_dev *dev, void *data)
{
struct pci_driver *pdrv;
const struct pci_error_handlers *err_handler;
device_lock(&dev->dev);
pdrv = dev->driver;
if (!pdrv || !pdrv->err_handler || !pdrv->err_handler->error_detected)
goto out;
err_handler = pdrv->err_handler;
err_handler->error_detected(dev, pci_channel_io_perm_failure);
out:
pci_uevent_ers(dev, PCI_ERS_RESULT_DISCONNECT);
device_unlock(&dev->dev);
return 0;
}
static int report_mmio_enabled(struct pci_dev *dev, void *data)
{
struct pci_driver *pdrv;
pci_ers_result_t vote, *result = data;
const struct pci_error_handlers *err_handler;
device_lock(&dev->dev);
pdrv = dev->driver;
if (!pdrv || !pdrv->err_handler || !pdrv->err_handler->mmio_enabled)
goto out;
err_handler = pdrv->err_handler;
vote = err_handler->mmio_enabled(dev);
*result = merge_result(*result, vote);
out:
device_unlock(&dev->dev);
return 0;
}
static int report_slot_reset(struct pci_dev *dev, void *data)
{
struct pci_driver *pdrv;
pci_ers_result_t vote, *result = data;
const struct pci_error_handlers *err_handler;
device_lock(&dev->dev);
pdrv = dev->driver;
if (!pci_dev_set_io_state(dev, pci_channel_io_normal) ||
!pdrv || !pdrv->err_handler || !pdrv->err_handler->slot_reset)
goto out;
err_handler = pdrv->err_handler;
vote = err_handler->slot_reset(dev);
*result = merge_result(*result, vote);
out:
device_unlock(&dev->dev);
return 0;
}
static int report_resume(struct pci_dev *dev, void *data)
{
struct pci_driver *pdrv;
const struct pci_error_handlers *err_handler;
device_lock(&dev->dev);
pdrv = dev->driver;
if (!pci_dev_set_io_state(dev, pci_channel_io_normal) ||
!pdrv || !pdrv->err_handler || !pdrv->err_handler->resume)
goto out;
err_handler = pdrv->err_handler;
err_handler->resume(dev);
out:
pci_uevent_ers(dev, PCI_ERS_RESULT_RECOVERED);
device_unlock(&dev->dev);
return 0;
}
/**
* pci_walk_bridge - walk bridges potentially AER affected
* @bridge: bridge which may be a Port, an RCEC, or an RCiEP
* @cb: callback to be called for each device found
* @userdata: arbitrary pointer to be passed to callback
*
* If the device provided is a bridge, walk the subordinate bus, including
* any bridged devices on buses under this bus. Call the provided callback
* on each device found.
*
* If the device provided has no subordinate bus, e.g., an RCEC or RCiEP,
* call the callback on the device itself.
*/
static void pci_walk_bridge(struct pci_dev *bridge,
int (*cb)(struct pci_dev *, void *),
void *userdata)
{
if (bridge->subordinate)
pci_walk_bus(bridge->subordinate, cb, userdata);
else
cb(bridge, userdata);
}
pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
pci_channel_state_t state,
pci_ers_result_t (*reset_subordinates)(struct pci_dev *pdev))
{
int type = pci_pcie_type(dev);
struct pci_dev *bridge;
pci_ers_result_t status = PCI_ERS_RESULT_CAN_RECOVER;
struct pci_host_bridge *host = pci_find_host_bridge(dev->bus);
/*
* If the error was detected by a Root Port, Downstream Port, RCEC,
* or RCiEP, recovery runs on the device itself. For Ports, that
* also includes any subordinate devices.
*
* If it was detected by another device (Endpoint, etc), recovery
* runs on the device and anything else under the same Port, i.e.,
* everything under "bridge".
*/
if (type == PCI_EXP_TYPE_ROOT_PORT ||
type == PCI_EXP_TYPE_DOWNSTREAM ||
type == PCI_EXP_TYPE_RC_EC ||
type == PCI_EXP_TYPE_RC_END)
bridge = dev;
else
bridge = pci_upstream_bridge(dev);
pci_walk_bridge(bridge, pci_pm_runtime_get_sync, NULL);
pci_dbg(bridge, "broadcast error_detected message\n");
if (state == pci_channel_io_frozen)
pci_walk_bridge(bridge, report_frozen_detected, &status);
else
pci_walk_bridge(bridge, report_normal_detected, &status);
if (status == PCI_ERS_RESULT_CAN_RECOVER) {
status = PCI_ERS_RESULT_RECOVERED;
pci_dbg(bridge, "broadcast mmio_enabled message\n");
pci_walk_bridge(bridge, report_mmio_enabled, &status);
}
if (status == PCI_ERS_RESULT_NEED_RESET ||
state == pci_channel_io_frozen) {
if (reset_subordinates(bridge) != PCI_ERS_RESULT_RECOVERED) {
pci_warn(bridge, "subordinate device reset failed\n");
goto failed;
}
}
if (status == PCI_ERS_RESULT_NEED_RESET) {
/*
* TODO: Should call platform-specific
* functions to reset slot before calling
* drivers' slot_reset callbacks?
*/
status = PCI_ERS_RESULT_RECOVERED;
pci_dbg(bridge, "broadcast slot_reset message\n");
pci_walk_bridge(bridge, report_slot_reset, &status);
}
if (status != PCI_ERS_RESULT_RECOVERED)
goto failed;
pci_dbg(bridge, "broadcast resume message\n");
pci_walk_bridge(bridge, report_resume, &status);
/*
* If we have native control of AER, clear error status in the device
* that detected the error. If the platform retained control of AER,
* it is responsible for clearing this status. In that case, the
* signaling device may not even be visible to the OS.
*/
if (host->native_aer || pcie_ports_native) {
pcie_clear_device_status(dev);
pci_aer_clear_nonfatal_status(dev);
}
pci_walk_bridge(bridge, pci_pm_runtime_put, NULL);
pci_info(bridge, "device recovery successful\n");
return status;
failed:
pci_walk_bridge(bridge, pci_pm_runtime_put, NULL);
pci_walk_bridge(bridge, report_perm_failure_detected, NULL);
pci_info(bridge, "device recovery failed\n");
return status;
}
+473
View File
@@ -0,0 +1,473 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCIe Native PME support
*
* Copyright (C) 2007 - 2009 Intel Corp
* Copyright (C) 2007 - 2009 Shaohua Li <shaohua.li@intel.com>
* Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
*/
#define dev_fmt(fmt) "PME: " fmt
#include <linux/bitfield.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/pm_runtime.h>
#include "../pci.h"
#include "portdrv.h"
/*
* If this switch is set, MSI will not be used for PCIe PME signaling. This
* causes the PCIe port driver to use INTx interrupts only, but it turns out
* that using MSI for PCIe PME signaling doesn't play well with PCIe PME-based
* wake-up from system sleep states.
*/
bool pcie_pme_msi_disabled;
static int __init pcie_pme_setup(char *str)
{
if (!strncmp(str, "nomsi", 5))
pcie_pme_msi_disabled = true;
return 1;
}
__setup("pcie_pme=", pcie_pme_setup);
struct pcie_pme_service_data {
spinlock_t lock;
struct pcie_device *srv;
struct work_struct work;
bool noirq; /* If set, keep the PME interrupt disabled. */
};
/**
* pcie_pme_interrupt_enable - Enable/disable PCIe PME interrupt generation.
* @dev: PCIe root port or event collector.
* @enable: Enable or disable the interrupt.
*/
void pcie_pme_interrupt_enable(struct pci_dev *dev, bool enable)
{
if (enable)
pcie_capability_set_word(dev, PCI_EXP_RTCTL,
PCI_EXP_RTCTL_PMEIE);
else
pcie_capability_clear_word(dev, PCI_EXP_RTCTL,
PCI_EXP_RTCTL_PMEIE);
}
/**
* pcie_pme_walk_bus - Scan a PCI bus for devices asserting PME#.
* @bus: PCI bus to scan.
*
* Scan given PCI bus and all buses under it for devices asserting PME#.
*/
static bool pcie_pme_walk_bus(struct pci_bus *bus)
{
struct pci_dev *dev;
bool ret = false;
list_for_each_entry(dev, &bus->devices, bus_list) {
/* Skip PCIe devices in case we started from a root port. */
if (!pci_is_pcie(dev) && pci_check_pme_status(dev)) {
if (dev->pme_poll)
dev->pme_poll = false;
pci_wakeup_event(dev);
pm_request_resume(&dev->dev);
ret = true;
}
if (dev->subordinate && pcie_pme_walk_bus(dev->subordinate))
ret = true;
}
return ret;
}
/**
* pcie_pme_from_pci_bridge - Check if PCIe-PCI bridge generated a PME.
* @bus: Secondary bus of the bridge.
* @devfn: Device/function number to check.
*
* PME from PCI devices under a PCIe-PCI bridge may be converted to an in-band
* PCIe PME message. In such that case the bridge should use the Requester ID
* of device/function number 0 on its secondary bus.
*/
static bool pcie_pme_from_pci_bridge(struct pci_bus *bus, u8 devfn)
{
struct pci_dev *dev;
bool found = false;
if (devfn)
return false;
dev = pci_dev_get(bus->self);
if (!dev)
return false;
if (pci_is_pcie(dev) && pci_pcie_type(dev) == PCI_EXP_TYPE_PCI_BRIDGE) {
down_read(&pci_bus_sem);
if (pcie_pme_walk_bus(bus))
found = true;
up_read(&pci_bus_sem);
}
pci_dev_put(dev);
return found;
}
/**
* pcie_pme_handle_request - Find device that generated PME and handle it.
* @port: Root port or event collector that generated the PME interrupt.
* @req_id: PCIe Requester ID of the device that generated the PME.
*/
static void pcie_pme_handle_request(struct pci_dev *port, u16 req_id)
{
u8 busnr = req_id >> 8, devfn = req_id & 0xff;
struct pci_bus *bus;
struct pci_dev *dev;
bool found = false;
/* First, check if the PME is from the root port itself. */
if (port->devfn == devfn && port->bus->number == busnr) {
if (port->pme_poll)
port->pme_poll = false;
if (pci_check_pme_status(port)) {
pm_request_resume(&port->dev);
found = true;
} else {
/*
* Apparently, the root port generated the PME on behalf
* of a non-PCIe device downstream. If this is done by
* a root port, the Requester ID field in its status
* register may contain either the root port's, or the
* source device's information (PCI Express Base
* Specification, Rev. 2.0, Section 6.1.9).
*/
down_read(&pci_bus_sem);
found = pcie_pme_walk_bus(port->subordinate);
up_read(&pci_bus_sem);
}
goto out;
}
/* Second, find the bus the source device is on. */
bus = pci_find_bus(pci_domain_nr(port->bus), busnr);
if (!bus)
goto out;
/* Next, check if the PME is from a PCIe-PCI bridge. */
found = pcie_pme_from_pci_bridge(bus, devfn);
if (found)
goto out;
/* Finally, try to find the PME source on the bus. */
down_read(&pci_bus_sem);
list_for_each_entry(dev, &bus->devices, bus_list) {
pci_dev_get(dev);
if (dev->devfn == devfn) {
found = true;
break;
}
pci_dev_put(dev);
}
up_read(&pci_bus_sem);
if (found) {
/* The device is there, but we have to check its PME status. */
found = pci_check_pme_status(dev);
if (found) {
if (dev->pme_poll)
dev->pme_poll = false;
pci_wakeup_event(dev);
pm_request_resume(&dev->dev);
}
pci_dev_put(dev);
} else if (devfn) {
/*
* The device is not there, but we can still try to recover by
* assuming that the PME was reported by a PCIe-PCI bridge that
* used devfn different from zero.
*/
pci_info(port, "interrupt generated for non-existent device %02x:%02x.%d\n",
busnr, PCI_SLOT(devfn), PCI_FUNC(devfn));
found = pcie_pme_from_pci_bridge(bus, 0);
}
out:
if (!found)
pci_info(port, "Spurious native interrupt!\n");
}
/**
* pcie_pme_work_fn - Work handler for PCIe PME interrupt.
* @work: Work structure giving access to service data.
*/
static void pcie_pme_work_fn(struct work_struct *work)
{
struct pcie_pme_service_data *data =
container_of(work, struct pcie_pme_service_data, work);
struct pci_dev *port = data->srv->port;
u32 rtsta;
spin_lock_irq(&data->lock);
for (;;) {
if (data->noirq)
break;
pcie_capability_read_dword(port, PCI_EXP_RTSTA, &rtsta);
if (PCI_POSSIBLE_ERROR(rtsta))
break;
if (rtsta & PCI_EXP_RTSTA_PME) {
/*
* Clear PME status of the port. If there are other
* pending PMEs, the status will be set again.
*/
pcie_clear_root_pme_status(port);
spin_unlock_irq(&data->lock);
pcie_pme_handle_request(port,
FIELD_GET(PCI_EXP_RTSTA_PME_RQ_ID, rtsta));
spin_lock_irq(&data->lock);
continue;
}
/* No need to loop if there are no more PMEs pending. */
if (!(rtsta & PCI_EXP_RTSTA_PENDING))
break;
spin_unlock_irq(&data->lock);
cpu_relax();
spin_lock_irq(&data->lock);
}
if (!data->noirq)
pcie_pme_interrupt_enable(port, true);
spin_unlock_irq(&data->lock);
}
/**
* pcie_pme_irq - Interrupt handler for PCIe root port PME interrupt.
* @irq: Interrupt vector.
* @context: Interrupt context pointer.
*/
static irqreturn_t pcie_pme_irq(int irq, void *context)
{
struct pci_dev *port;
struct pcie_pme_service_data *data;
u32 rtsta;
unsigned long flags;
port = ((struct pcie_device *)context)->port;
data = get_service_data((struct pcie_device *)context);
spin_lock_irqsave(&data->lock, flags);
pcie_capability_read_dword(port, PCI_EXP_RTSTA, &rtsta);
if (PCI_POSSIBLE_ERROR(rtsta) || !(rtsta & PCI_EXP_RTSTA_PME)) {
spin_unlock_irqrestore(&data->lock, flags);
return IRQ_NONE;
}
pcie_pme_interrupt_enable(port, false);
spin_unlock_irqrestore(&data->lock, flags);
/* We don't use pm_wq, because it's freezable. */
schedule_work(&data->work);
return IRQ_HANDLED;
}
/**
* pcie_pme_can_wakeup - Set the wakeup capability flag.
* @dev: PCI device to handle.
* @ign: Ignored.
*/
static int pcie_pme_can_wakeup(struct pci_dev *dev, void *ign)
{
device_set_wakeup_capable(&dev->dev, true);
return 0;
}
/**
* pcie_pme_mark_devices - Set the wakeup flag for devices below a port.
* @port: PCIe root port or event collector to handle.
*
* For each device below given root port, including the port itself (or for each
* root complex integrated endpoint if @port is a root complex event collector)
* set the flag indicating that it can signal run-time wake-up events.
*/
static void pcie_pme_mark_devices(struct pci_dev *port)
{
pcie_pme_can_wakeup(port, NULL);
if (pci_pcie_type(port) == PCI_EXP_TYPE_RC_EC)
pcie_walk_rcec(port, pcie_pme_can_wakeup, NULL);
else if (port->subordinate)
pci_walk_bus(port->subordinate, pcie_pme_can_wakeup, NULL);
}
/**
* pcie_pme_probe - Initialize PCIe PME service for given root port.
* @srv: PCIe service to initialize.
*/
static int pcie_pme_probe(struct pcie_device *srv)
{
struct pci_dev *port = srv->port;
struct pcie_pme_service_data *data;
int type = pci_pcie_type(port);
int ret;
/* Limit to Root Ports or Root Complex Event Collectors */
if (type != PCI_EXP_TYPE_RC_EC &&
type != PCI_EXP_TYPE_ROOT_PORT)
return -ENODEV;
data = kzalloc_obj(*data);
if (!data)
return -ENOMEM;
spin_lock_init(&data->lock);
INIT_WORK(&data->work, pcie_pme_work_fn);
data->srv = srv;
set_service_data(srv, data);
pcie_pme_interrupt_enable(port, false);
pcie_clear_root_pme_status(port);
ret = request_irq(srv->irq, pcie_pme_irq, IRQF_SHARED, "PCIe PME", srv);
if (ret) {
kfree(data);
return ret;
}
pci_info(port, "Signaling with IRQ %d\n", srv->irq);
pcie_pme_mark_devices(port);
pcie_pme_interrupt_enable(port, true);
return 0;
}
static bool pcie_pme_check_wakeup(struct pci_bus *bus)
{
struct pci_dev *dev;
if (!bus)
return false;
list_for_each_entry(dev, &bus->devices, bus_list)
if (device_may_wakeup(&dev->dev)
|| pcie_pme_check_wakeup(dev->subordinate))
return true;
return false;
}
static void pcie_pme_disable_interrupt(struct pci_dev *port,
struct pcie_pme_service_data *data)
{
spin_lock_irq(&data->lock);
pcie_pme_interrupt_enable(port, false);
pcie_clear_root_pme_status(port);
data->noirq = true;
spin_unlock_irq(&data->lock);
}
/**
* pcie_pme_suspend - Suspend PCIe PME service device.
* @srv: PCIe service device to suspend.
*/
static int pcie_pme_suspend(struct pcie_device *srv)
{
struct pcie_pme_service_data *data = get_service_data(srv);
struct pci_dev *port = srv->port;
bool wakeup;
int ret;
if (device_may_wakeup(&port->dev)) {
wakeup = true;
} else {
down_read(&pci_bus_sem);
wakeup = pcie_pme_check_wakeup(port->subordinate);
up_read(&pci_bus_sem);
}
if (wakeup) {
ret = enable_irq_wake(srv->irq);
if (!ret)
return 0;
}
pcie_pme_disable_interrupt(port, data);
synchronize_irq(srv->irq);
return 0;
}
/**
* pcie_pme_resume - Resume PCIe PME service device.
* @srv: PCIe service device to resume.
*/
static int pcie_pme_resume(struct pcie_device *srv)
{
struct pcie_pme_service_data *data = get_service_data(srv);
spin_lock_irq(&data->lock);
if (data->noirq) {
struct pci_dev *port = srv->port;
pcie_clear_root_pme_status(port);
pcie_pme_interrupt_enable(port, true);
data->noirq = false;
} else {
disable_irq_wake(srv->irq);
}
spin_unlock_irq(&data->lock);
return 0;
}
/**
* pcie_pme_remove - Prepare PCIe PME service device for removal.
* @srv: PCIe service device to remove.
*/
static void pcie_pme_remove(struct pcie_device *srv)
{
struct pcie_pme_service_data *data = get_service_data(srv);
pcie_pme_disable_interrupt(srv->port, data);
free_irq(srv->irq, srv);
cancel_work_sync(&data->work);
kfree(data);
}
static struct pcie_port_service_driver pcie_pme_driver = {
.name = "pcie_pme",
.port_type = PCIE_ANY_PORT,
.service = PCIE_PORT_SERVICE_PME,
.probe = pcie_pme_probe,
.suspend = pcie_pme_suspend,
.resume = pcie_pme_resume,
.remove = pcie_pme_remove,
};
/**
* pcie_pme_init - Register the PCIe PME service driver.
*/
int __init pcie_pme_init(void)
{
return pcie_port_service_register(&pcie_pme_driver);
}
+849
View File
@@ -0,0 +1,849 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Purpose: PCI Express Port Bus Driver
*
* Copyright (C) 2004 Intel
* Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com)
*/
#include <linux/bitfield.h>
#include <linux/dmi.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/aer.h>
#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;
u32 service;
};
/**
* release_pcie_device - free PCI Express port service device structure
* @dev: Port service device to release
*
* Invoked automatically when device is being removed in response to
* device_unregister(dev). Release all resources being claimed.
*/
static void release_pcie_device(struct device *dev)
{
kfree(to_pcie_device(dev));
}
/*
* Fill in *pme, *aer, *dpc with the relevant Interrupt Message Numbers if
* services are enabled in "mask". Return the number of MSI/MSI-X vectors
* required to accommodate the largest Message Number.
*/
static int pcie_message_numbers(struct pci_dev *dev, int mask,
u32 *pme, u32 *aer, u32 *dpc)
{
u32 nvec = 0, pos;
u16 reg16;
/*
* The Interrupt Message Number indicates which vector is used, i.e.,
* the MSI-X table entry or the MSI offset between the base Message
* Data and the generated interrupt message. See PCIe r3.1, sec
* 7.8.2, 7.10.10, 7.31.2.
*/
if (mask & (PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP |
PCIE_PORT_SERVICE_BWCTRL)) {
pcie_capability_read_word(dev, PCI_EXP_FLAGS, &reg16);
*pme = FIELD_GET(PCI_EXP_FLAGS_IRQ, reg16);
nvec = *pme + 1;
}
#ifdef CONFIG_PCIEAER
if (mask & PCIE_PORT_SERVICE_AER) {
u32 reg32;
pos = dev->aer_cap;
if (pos) {
pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS,
&reg32);
*aer = FIELD_GET(PCI_ERR_ROOT_AER_IRQ, reg32);
nvec = max(nvec, *aer + 1);
}
}
#endif
if (mask & PCIE_PORT_SERVICE_DPC) {
pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_DPC);
if (pos) {
pci_read_config_word(dev, pos + PCI_EXP_DPC_CAP,
&reg16);
*dpc = FIELD_GET(PCI_EXP_DPC_IRQ, reg16);
nvec = max(nvec, *dpc + 1);
}
}
return nvec;
}
/**
* pcie_port_enable_irq_vec - try to set up MSI-X or MSI as interrupt mode
* for given port
* @dev: PCI Express port to handle
* @irqs: Array of interrupt vectors to populate
* @mask: Bitmask of port capabilities returned by get_port_device_capability()
*
* Return value: 0 on success, error code on failure
*/
static int pcie_port_enable_irq_vec(struct pci_dev *dev, int *irqs, int mask)
{
int nr_entries, nvec, pcie_irq;
u32 pme = 0, aer = 0, dpc = 0;
/* Allocate the maximum possible number of MSI/MSI-X vectors */
nr_entries = pci_alloc_irq_vectors(dev, 1, PCIE_PORT_MAX_MSI_ENTRIES,
PCI_IRQ_MSIX | PCI_IRQ_MSI);
if (nr_entries < 0)
return nr_entries;
/* See how many and which Interrupt Message Numbers we actually use */
nvec = pcie_message_numbers(dev, mask, &pme, &aer, &dpc);
if (nvec > nr_entries) {
pci_free_irq_vectors(dev);
return -EIO;
}
/*
* If we allocated more than we need, free them and reallocate fewer.
*
* Reallocating may change the specific vectors we get, so
* pci_irq_vector() must be done *after* the reallocation.
*
* If we're using MSI, hardware is *allowed* to change the Interrupt
* Message Numbers when we free and reallocate the vectors, but we
* assume it won't because we allocate enough vectors for the
* biggest Message Number we found.
*/
if (nvec != nr_entries) {
pci_free_irq_vectors(dev);
nr_entries = pci_alloc_irq_vectors(dev, nvec, nvec,
PCI_IRQ_MSIX | PCI_IRQ_MSI);
if (nr_entries < 0)
return nr_entries;
}
/* PME, hotplug and bandwidth notification share an MSI/MSI-X vector */
if (mask & (PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP |
PCIE_PORT_SERVICE_BWCTRL)) {
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_BWCTRL_SHIFT] = pcie_irq;
}
if (mask & PCIE_PORT_SERVICE_AER)
irqs[PCIE_PORT_SERVICE_AER_SHIFT] = pci_irq_vector(dev, aer);
if (mask & PCIE_PORT_SERVICE_DPC)
irqs[PCIE_PORT_SERVICE_DPC_SHIFT] = pci_irq_vector(dev, dpc);
return 0;
}
/**
* pcie_init_service_irqs - initialize irqs for PCI Express port services
* @dev: PCI Express port to handle
* @irqs: Array of irqs to populate
* @mask: Bitmask of port capabilities returned by get_port_device_capability()
*
* Return value: Interrupt mode associated with the port
*/
static int pcie_init_service_irqs(struct pci_dev *dev, int *irqs, int mask)
{
int ret, i;
for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
irqs[i] = -1;
/*
* If we support PME but can't use MSI/MSI-X for it, we have to
* fall back to INTx or other interrupts, e.g., a system shared
* interrupt.
*/
if ((mask & PCIE_PORT_SERVICE_PME) && pcie_pme_no_msi())
goto intx_irq;
/* Try to use MSI-X or MSI if supported */
if (pcie_port_enable_irq_vec(dev, irqs, mask) == 0)
return 0;
intx_irq:
/* fall back to INTX IRQ */
ret = pci_alloc_irq_vectors(dev, 1, 1, PCI_IRQ_INTX);
if (ret < 0)
return -ENODEV;
for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
irqs[i] = pci_irq_vector(dev, 0);
return 0;
}
/**
* get_port_device_capability - discover capabilities of a PCI Express port
* @dev: PCI Express port to examine
*
* The capabilities are read from the port's PCI Express configuration registers
* as described in PCI Express Base Specification 1.0a sections 7.8.2, 7.8.9 and
* 7.9 - 7.11.
*
* Return value: Bitmask of discovered port capabilities
*/
static int get_port_device_capability(struct pci_dev *dev)
{
struct pci_host_bridge *host = pci_find_host_bridge(dev->bus);
int services = 0;
if (dev->is_pciehp &&
(pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT ||
pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM) &&
(pcie_ports_native || host->native_pcie_hotplug)) {
services |= PCIE_PORT_SERVICE_HP;
/*
* Disable hot-plug interrupts in case they have been enabled
* by the BIOS and the hot-plug service driver won't be loaded
* to handle them.
*/
if (!IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE))
pcie_capability_clear_word(dev, PCI_EXP_SLTCTL,
PCI_EXP_SLTCTL_CCIE | PCI_EXP_SLTCTL_HPIE);
}
#ifdef CONFIG_PCIEAER
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
/* Root Ports and Root Complex Event Collectors may generate PMEs */
if ((pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT ||
pci_pcie_type(dev) == PCI_EXP_TYPE_RC_EC) &&
(pcie_ports_native || host->native_pme)) {
services |= PCIE_PORT_SERVICE_PME;
/*
* Disable PME interrupt on this port in case it's been enabled
* by the BIOS (the PME service driver will enable it when
* necessary).
*/
pcie_pme_interrupt_enable(dev, false);
}
/*
* With dpc-native, allow Linux to use DPC even if it doesn't have
* permission to use AER.
*/
if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_DPC) &&
pci_aer_available() &&
(pcie_ports_dpc_native || (services & PCIE_PORT_SERVICE_AER)))
services |= PCIE_PORT_SERVICE_DPC;
/* Enable bandwidth control if more than one speed is supported. */
if (pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM ||
pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT) {
u32 linkcap;
pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &linkcap);
if (linkcap & PCI_EXP_LNKCAP_LBNC &&
hweight8(dev->supported_speeds) > 1)
services |= PCIE_PORT_SERVICE_BWCTRL;
}
return services;
}
/**
* pcie_device_init - allocate and initialize PCI Express port service device
* @pdev: PCI Express port to associate the service device with
* @service: Type of service to associate with the service device
* @irq: Interrupt vector to associate with the service device
*/
static int pcie_device_init(struct pci_dev *pdev, int service, int irq)
{
int retval;
struct pcie_device *pcie;
struct device *device;
pcie = kzalloc_obj(*pcie);
if (!pcie)
return -ENOMEM;
pcie->port = pdev;
pcie->irq = irq;
pcie->service = service;
/* Initialize generic device interface */
device = &pcie->device;
device->bus = &pcie_port_bus_type;
device->release = release_pcie_device; /* callback to free pcie dev */
dev_set_name(device, "%s:pcie%03x",
pci_name(pdev),
get_descriptor_id(pci_pcie_type(pdev), service));
device->parent = &pdev->dev;
device_enable_async_suspend(device);
retval = device_register(device);
if (retval) {
put_device(device);
return retval;
}
pm_runtime_no_callbacks(device);
return 0;
}
/**
* pcie_port_device_register - register PCI Express port
* @dev: PCI Express port to register
*
* Allocate the port extension structure and register services associated with
* the port.
*/
static int pcie_port_device_register(struct pci_dev *dev)
{
int status, capabilities, i, nr_service;
int irqs[PCIE_PORT_DEVICE_MAXSERVICES];
/* Enable PCI Express port device */
status = pci_enable_device(dev);
if (status)
return status;
/* Get and check PCI Express port services */
capabilities = get_port_device_capability(dev);
if (!capabilities)
return 0;
pci_set_master(dev);
/*
* Initialize service irqs. Don't use service devices that
* require interrupts if there is no way to generate them.
* However, some drivers may have a polling mode (e.g. pciehp_poll_mode)
* that can be used in the absence of irqs. Allow them to determine
* if that is to be used.
*/
status = pcie_init_service_irqs(dev, irqs, capabilities);
if (status) {
capabilities &= PCIE_PORT_SERVICE_HP;
if (!capabilities)
goto error_disable;
}
/* Allocate child services if any */
status = -ENODEV;
nr_service = 0;
for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
int service = 1 << i;
if (!(capabilities & service))
continue;
if (!pcie_device_init(dev, service, irqs[i]))
nr_service++;
}
if (!nr_service)
goto error_cleanup_irqs;
return 0;
error_cleanup_irqs:
pci_free_irq_vectors(dev);
error_disable:
pci_disable_device(dev);
return status;
}
typedef int (*pcie_callback_t)(struct pcie_device *);
static int pcie_port_device_iter(struct device *dev, void *data)
{
struct pcie_port_service_driver *service_driver;
size_t offset = *(size_t *)data;
pcie_callback_t cb;
if ((dev->bus == &pcie_port_bus_type) && dev->driver) {
service_driver = to_service_driver(dev->driver);
cb = *(pcie_callback_t *)((void *)service_driver + offset);
if (cb)
return cb(to_pcie_device(dev));
}
return 0;
}
#ifdef CONFIG_PM
/**
* pcie_port_device_suspend - suspend port services associated with a PCIe port
* @dev: PCI Express port to handle
*/
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);
}
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);
}
/**
* pcie_port_device_resume - resume port services associated with a PCIe port
* @dev: PCI Express port to handle
*/
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);
}
/**
* pcie_port_device_runtime_suspend - runtime suspend port services
* @dev: PCI Express port to handle
*/
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);
}
/**
* pcie_port_device_runtime_resume - runtime resume port services
* @dev: PCI Express port to handle
*/
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);
}
#endif /* PM */
static int remove_iter(struct device *dev, void *data)
{
if (dev->bus == &pcie_port_bus_type)
device_unregister(dev);
return 0;
}
static int find_service_iter(struct device *device, void *data)
{
struct pcie_port_service_driver *service_driver;
struct portdrv_service_data *pdrvs;
u32 service;
pdrvs = (struct portdrv_service_data *) data;
service = pdrvs->service;
if (device->bus == &pcie_port_bus_type && device->driver) {
service_driver = to_service_driver(device->driver);
if (service_driver->service == service) {
pdrvs->drv = service_driver;
pdrvs->dev = device;
return 1;
}
}
return 0;
}
/**
* pcie_port_find_device - find the struct device
* @dev: PCI Express port the service is associated with
* @service: For the service to find
*
* Find the struct device associated with given service on a pci_dev
*/
struct device *pcie_port_find_device(struct pci_dev *dev,
u32 service)
{
struct device *device;
struct portdrv_service_data pdrvs;
pdrvs.dev = NULL;
pdrvs.service = service;
device_for_each_child(&dev->dev, &pdrvs, find_service_iter);
device = pdrvs.dev;
return device;
}
EXPORT_SYMBOL_GPL(pcie_port_find_device);
/**
* pcie_port_device_remove - unregister PCI Express port service devices
* @dev: PCI Express port the service devices to unregister are associated with
*
* Remove PCI Express port service devices associated with given port and
* disable MSI-X or MSI for the port.
*/
static void pcie_port_device_remove(struct pci_dev *dev)
{
device_for_each_child(&dev->dev, NULL, remove_iter);
pci_free_irq_vectors(dev);
}
static int pcie_port_bus_match(struct device *dev, const struct device_driver *drv)
{
struct pcie_device *pciedev = to_pcie_device(dev);
const struct pcie_port_service_driver *driver = to_service_driver(drv);
if (driver->service != pciedev->service)
return 0;
if (driver->port_type != PCIE_ANY_PORT &&
driver->port_type != pci_pcie_type(pciedev->port))
return 0;
return 1;
}
/**
* pcie_port_bus_probe - probe driver for given PCI Express port service
* @dev: PCI Express port service device to probe against
*
* If PCI Express port service driver is registered with
* pcie_port_service_register(), this function will be called by the driver core
* whenever match is found between the driver and a port service device.
*/
static int pcie_port_bus_probe(struct device *dev)
{
struct pcie_device *pciedev;
struct pcie_port_service_driver *driver;
int status;
driver = to_service_driver(dev->driver);
if (!driver || !driver->probe)
return -ENODEV;
pciedev = to_pcie_device(dev);
status = driver->probe(pciedev);
if (status)
return status;
get_device(dev);
return 0;
}
/**
* pcie_port_bus_remove - detach driver from given PCI Express port service
* @dev: PCI Express port service device to handle
*
* If PCI Express port service driver is registered with
* pcie_port_service_register(), this function will be called by the driver core
* when device_unregister() is called for the port service device associated
* with the driver.
*/
static void pcie_port_bus_remove(struct device *dev)
{
struct pcie_device *pciedev;
struct pcie_port_service_driver *driver;
pciedev = to_pcie_device(dev);
driver = to_service_driver(dev->driver);
if (driver && driver->remove)
driver->remove(pciedev);
put_device(dev);
}
const struct bus_type pcie_port_bus_type = {
.name = "pci_express",
.match = pcie_port_bus_match,
.probe = pcie_port_bus_probe,
.remove = pcie_port_bus_remove,
};
/**
* pcie_port_service_register - register PCI Express port service driver
* @new: PCI Express port service driver to register
*/
int pcie_port_service_register(struct pcie_port_service_driver *new)
{
if (pcie_ports_disabled)
return -ENODEV;
new->driver.name = new->name;
new->driver.bus = &pcie_port_bus_type;
return driver_register(&new->driver);
}
/**
* pcie_port_service_unregister - unregister PCI Express port service driver
* @drv: PCI Express port service driver to unregister
*/
void pcie_port_service_unregister(struct pcie_port_service_driver *drv)
{
driver_unregister(&drv->driver);
}
/* 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);
pci_disable_device(dev);
}
static void pcie_portdrv_shutdown(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);
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,
.probe = pcie_portdrv_probe,
.remove = pcie_portdrv_remove,
.shutdown = pcie_portdrv_shutdown,
.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_bwctrl_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);
+138
View File
@@ -0,0 +1,138 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Purpose: PCI Express Port Bus Driver's Internal Data Structures
*
* Copyright (C) 2004 Intel
* Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com)
*/
#ifndef _PORTDRV_H_
#define _PORTDRV_H_
#include <linux/compiler.h>
/* Service Type */
#define PCIE_PORT_SERVICE_PME_SHIFT 0 /* Power Management Event */
#define PCIE_PORT_SERVICE_PME (1 << PCIE_PORT_SERVICE_PME_SHIFT)
#define PCIE_PORT_SERVICE_AER_SHIFT 1 /* Advanced Error Reporting */
#define PCIE_PORT_SERVICE_AER (1 << PCIE_PORT_SERVICE_AER_SHIFT)
#define PCIE_PORT_SERVICE_HP_SHIFT 2 /* Native Hotplug */
#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_BWCTRL_SHIFT 4 /* Bandwidth Controller (notifications) */
#define PCIE_PORT_SERVICE_BWCTRL (1 << PCIE_PORT_SERVICE_BWCTRL_SHIFT)
#define PCIE_PORT_DEVICE_MAXSERVICES 5
extern bool pcie_ports_dpc_native;
#ifdef CONFIG_PCIEAER
int pcie_aer_init(void);
#else
static inline int pcie_aer_init(void) { return 0; }
#endif
#ifdef CONFIG_HOTPLUG_PCI_PCIE
int pcie_hp_init(void);
#else
static inline int pcie_hp_init(void) { return 0; }
#endif
#ifdef CONFIG_PCIE_PME
int pcie_pme_init(void);
#else
static inline int pcie_pme_init(void) { return 0; }
#endif
#ifdef CONFIG_PCIE_DPC
int pcie_dpc_init(void);
#else
static inline int pcie_dpc_init(void) { return 0; }
#endif
int pcie_bwctrl_init(void);
/* Port Type */
#define PCIE_ANY_PORT (~0)
struct pcie_device {
int irq; /* Service IRQ/MSI/MSI-X Vector */
struct pci_dev *port; /* Root/Upstream/Downstream Port */
u32 service; /* Port service this device represents */
void *priv_data; /* Service Private Data */
struct device device; /* Generic Device Interface */
};
#define to_pcie_device(d) container_of(d, struct pcie_device, device)
static inline void set_service_data(struct pcie_device *dev, void *data)
{
dev->priv_data = data;
}
static inline void *get_service_data(struct pcie_device *dev)
{
return dev->priv_data;
}
struct pcie_port_service_driver {
const char *name;
int (*probe)(struct pcie_device *dev);
void (*remove)(struct pcie_device *dev);
int (*suspend)(struct pcie_device *dev);
int (*resume_noirq)(struct pcie_device *dev);
int (*resume)(struct pcie_device *dev);
int (*runtime_suspend)(struct pcie_device *dev);
int (*runtime_resume)(struct pcie_device *dev);
int (*slot_reset)(struct pcie_device *dev);
int port_type; /* Type of the port this driver can handle */
u32 service; /* Port service this device represents */
struct device_driver driver;
};
#define to_service_driver(d) \
container_of(d, struct pcie_port_service_driver, driver)
int pcie_port_service_register(struct pcie_port_service_driver *new);
void pcie_port_service_unregister(struct pcie_port_service_driver *new);
extern const struct bus_type pcie_port_bus_type;
struct pci_dev;
#ifdef CONFIG_PCIE_PME
extern bool pcie_pme_msi_disabled;
static inline void pcie_pme_disable_msi(void)
{
pcie_pme_msi_disabled = true;
}
static inline bool pcie_pme_no_msi(void)
{
return pcie_pme_msi_disabled;
}
void pcie_pme_interrupt_enable(struct pci_dev *dev, bool enable);
#else /* !CONFIG_PCIE_PME */
static inline void pcie_pme_disable_msi(void) {}
static inline bool pcie_pme_no_msi(void) { return false; }
static inline void pcie_pme_interrupt_enable(struct pci_dev *dev, bool en) {}
#endif /* !CONFIG_PCIE_PME */
struct device *pcie_port_find_device(struct pci_dev *dev, u32 service);
struct aer_err_info;
#ifdef CONFIG_CXL_RAS
bool is_aer_internal_error(struct aer_err_info *info);
void cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info);
void cxl_rch_enable_rcec(struct pci_dev *rcec);
#else
static inline bool is_aer_internal_error(struct aer_err_info *info) { return false; }
static inline void cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info) { }
static inline void cxl_rch_enable_rcec(struct pci_dev *rcec) { }
#endif /* CONFIG_CXL_RAS */
#endif /* _PORTDRV_H_ */
+586
View File
@@ -0,0 +1,586 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI Express Precision Time Measurement
* Copyright (c) 2016, Intel Corporation.
*/
#include <linux/bitfield.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/pci.h>
#include "../pci.h"
/*
* If the next upstream device supports PTM, return it; otherwise return
* NULL. PTM Messages are local, so both link partners must support it.
*/
static struct pci_dev *pci_upstream_ptm(struct pci_dev *dev)
{
struct pci_dev *ups = pci_upstream_bridge(dev);
/*
* Switch Downstream Ports are not permitted to have a PTM
* capability; their PTM behavior is controlled by the Upstream
* Port (PCIe r5.0, sec 7.9.16), so if the upstream bridge is a
* Switch Downstream Port, look up one more level.
*/
if (ups && pci_pcie_type(ups) == PCI_EXP_TYPE_DOWNSTREAM)
ups = pci_upstream_bridge(ups);
if (ups && ups->ptm_cap)
return ups;
return NULL;
}
/*
* Find the PTM Capability (if present) and extract the information we need
* to use it.
*/
void pci_ptm_init(struct pci_dev *dev)
{
u16 ptm;
u32 cap;
struct pci_dev *ups;
if (!pci_is_pcie(dev))
return;
ptm = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_PTM);
if (!ptm)
return;
dev->ptm_cap = ptm;
atomic_set(&dev->ptm_enable_cnt, 0);
pci_add_ext_cap_save_buffer(dev, PCI_EXT_CAP_ID_PTM, sizeof(u32));
pci_read_config_dword(dev, ptm + PCI_PTM_CAP, &cap);
dev->ptm_granularity = FIELD_GET(PCI_PTM_GRANULARITY_MASK, cap);
/*
* Per the spec recommendation (PCIe r6.0, sec 7.9.15.3), select the
* furthest upstream Time Source as the PTM Root. For Endpoints,
* "the Effective Granularity is the maximum Local Clock Granularity
* reported by the PTM Root and all intervening PTM Time Sources."
*/
ups = pci_upstream_ptm(dev);
if (ups) {
if (ups->ptm_granularity == 0)
dev->ptm_granularity = 0;
else if (ups->ptm_granularity > dev->ptm_granularity)
dev->ptm_granularity = ups->ptm_granularity;
} else if (cap & PCI_PTM_CAP_ROOT) {
dev->ptm_root = 1;
} else if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END) {
/*
* Per sec 7.9.15.3, this should be the Local Clock
* Granularity of the associated Time Source. But it
* doesn't say how to find that Time Source.
*/
dev->ptm_granularity = 0;
}
if (cap & PCI_PTM_CAP_RES)
dev->ptm_responder = 1;
if (cap & PCI_PTM_CAP_REQ)
dev->ptm_requester = 1;
}
void pci_save_ptm_state(struct pci_dev *dev)
{
u16 ptm = dev->ptm_cap;
struct pci_cap_saved_state *save_state;
u32 *cap;
if (!ptm)
return;
save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_PTM);
if (!save_state)
return;
cap = (u32 *)&save_state->cap.data[0];
pci_read_config_dword(dev, ptm + PCI_PTM_CTRL, cap);
}
void pci_restore_ptm_state(struct pci_dev *dev)
{
u16 ptm = dev->ptm_cap;
struct pci_cap_saved_state *save_state;
u32 *cap;
if (!ptm)
return;
save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_PTM);
if (!save_state)
return;
cap = (u32 *)&save_state->cap.data[0];
pci_write_config_dword(dev, ptm + PCI_PTM_CTRL, *cap);
}
/* Enable PTM in the Control register if possible */
static int __pci_enable_ptm(struct pci_dev *dev)
{
u16 ptm = dev->ptm_cap;
u32 ctrl;
if (!ptm)
return -EINVAL;
switch (pci_pcie_type(dev)) {
case PCI_EXP_TYPE_ROOT_PORT:
if (!dev->ptm_root)
return -EINVAL;
break;
case PCI_EXP_TYPE_UPSTREAM:
if (!dev->ptm_responder)
return -EINVAL;
break;
case PCI_EXP_TYPE_ENDPOINT:
case PCI_EXP_TYPE_LEG_END:
if (!dev->ptm_requester)
return -EINVAL;
break;
default:
return -EINVAL;
}
pci_read_config_dword(dev, ptm + PCI_PTM_CTRL, &ctrl);
ctrl |= PCI_PTM_CTRL_ENABLE;
FIELD_MODIFY(PCI_PTM_GRANULARITY_MASK, &ctrl, dev->ptm_granularity);
if (dev->ptm_root)
ctrl |= PCI_PTM_CTRL_ROOT;
pci_write_config_dword(dev, ptm + PCI_PTM_CTRL, ctrl);
return 0;
}
/**
* pci_enable_ptm() - Enable Precision Time Measurement
* @dev: PCI device
*
* Enable Precision Time Measurement for @dev.
*
* Return: zero if successful, or -EINVAL if @dev lacks a PTM Capability or
* is not a PTM Root and lacks an upstream path of PTM-enabled devices.
*/
int pci_enable_ptm(struct pci_dev *dev)
{
int rc;
char clock_desc[8];
/*
* A device uses local PTM Messages to request time information
* from a PTM Root that's farther upstream. Every device along
* the path must support PTM and have it enabled so it can
* handle the messages. Therefore, if this device is not a PTM
* Root, the upstream link partner must have PTM enabled before
* we can enable PTM.
*/
if (!dev->ptm_root) {
struct pci_dev *parent;
parent = pci_upstream_ptm(dev);
if (!parent)
return -EINVAL;
/* Enable PTM for the parent */
rc = pci_enable_ptm(parent);
if (rc)
return rc;
}
/* Already enabled? */
if (atomic_inc_return(&dev->ptm_enable_cnt) > 1)
return 0;
rc = __pci_enable_ptm(dev);
if (rc) {
atomic_dec(&dev->ptm_enable_cnt);
return rc;
}
switch (dev->ptm_granularity) {
case 0:
snprintf(clock_desc, sizeof(clock_desc), "unknown");
break;
case 255:
snprintf(clock_desc, sizeof(clock_desc), ">254ns");
break;
default:
snprintf(clock_desc, sizeof(clock_desc), "%uns",
dev->ptm_granularity);
break;
}
pci_info(dev, "PTM enabled%s, %s granularity\n",
dev->ptm_root ? " (root)" : "", clock_desc);
return 0;
}
EXPORT_SYMBOL(pci_enable_ptm);
static void __pci_disable_ptm(struct pci_dev *dev)
{
u16 ptm = dev->ptm_cap;
u32 ctrl;
if (!ptm)
return;
pci_read_config_dword(dev, ptm + PCI_PTM_CTRL, &ctrl);
ctrl &= ~(PCI_PTM_CTRL_ENABLE | PCI_PTM_CTRL_ROOT);
pci_write_config_dword(dev, ptm + PCI_PTM_CTRL, ctrl);
}
/**
* pci_disable_ptm() - Disable Precision Time Measurement
* @dev: PCI device
*
* Disable Precision Time Measurement for @dev.
*/
void pci_disable_ptm(struct pci_dev *dev)
{
struct pci_dev *parent;
if (atomic_dec_and_test(&dev->ptm_enable_cnt))
__pci_disable_ptm(dev);
parent = pci_upstream_ptm(dev);
if (parent)
pci_disable_ptm(parent);
}
EXPORT_SYMBOL(pci_disable_ptm);
/*
* Disable PTM, but preserve dev->ptm_enable_cnt so we silently re-enable it on
* resume if necessary.
*/
void pci_suspend_ptm(struct pci_dev *dev)
{
if (atomic_read(&dev->ptm_enable_cnt))
__pci_disable_ptm(dev);
}
/* If PTM was enabled before suspend, re-enable it when resuming */
void pci_resume_ptm(struct pci_dev *dev)
{
if (atomic_read(&dev->ptm_enable_cnt))
__pci_enable_ptm(dev);
}
bool pcie_ptm_enabled(struct pci_dev *dev)
{
if (!dev)
return false;
return atomic_read(&dev->ptm_enable_cnt);
}
EXPORT_SYMBOL(pcie_ptm_enabled);
#if IS_ENABLED(CONFIG_DEBUG_FS)
static ssize_t context_update_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct pci_ptm_debugfs *ptm_debugfs = file->private_data;
char buf[7];
int ret;
u8 mode;
if (!ptm_debugfs->ops->context_update_write)
return -EOPNOTSUPP;
if (count < 1 || count >= sizeof(buf))
return -EINVAL;
ret = copy_from_user(buf, ubuf, count);
if (ret)
return -EFAULT;
buf[count] = '\0';
if (sysfs_streq(buf, "auto"))
mode = PCIE_PTM_CONTEXT_UPDATE_AUTO;
else if (sysfs_streq(buf, "manual"))
mode = PCIE_PTM_CONTEXT_UPDATE_MANUAL;
else
return -EINVAL;
mutex_lock(&ptm_debugfs->lock);
ret = ptm_debugfs->ops->context_update_write(ptm_debugfs->pdata, mode);
mutex_unlock(&ptm_debugfs->lock);
if (ret)
return ret;
return count;
}
static ssize_t context_update_read(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct pci_ptm_debugfs *ptm_debugfs = file->private_data;
char buf[8]; /* Extra space for NULL termination at the end */
ssize_t pos;
u8 mode;
if (!ptm_debugfs->ops->context_update_read)
return -EOPNOTSUPP;
mutex_lock(&ptm_debugfs->lock);
ptm_debugfs->ops->context_update_read(ptm_debugfs->pdata, &mode);
mutex_unlock(&ptm_debugfs->lock);
if (mode == PCIE_PTM_CONTEXT_UPDATE_AUTO)
pos = scnprintf(buf, sizeof(buf), "auto\n");
else
pos = scnprintf(buf, sizeof(buf), "manual\n");
return simple_read_from_buffer(ubuf, count, ppos, buf, pos);
}
static const struct file_operations context_update_fops = {
.open = simple_open,
.read = context_update_read,
.write = context_update_write,
};
static int context_valid_get(void *data, u64 *val)
{
struct pci_ptm_debugfs *ptm_debugfs = data;
bool valid;
int ret;
if (!ptm_debugfs->ops->context_valid_read)
return -EOPNOTSUPP;
mutex_lock(&ptm_debugfs->lock);
ret = ptm_debugfs->ops->context_valid_read(ptm_debugfs->pdata, &valid);
mutex_unlock(&ptm_debugfs->lock);
if (ret)
return ret;
*val = valid;
return 0;
}
static int context_valid_set(void *data, u64 val)
{
struct pci_ptm_debugfs *ptm_debugfs = data;
int ret;
if (!ptm_debugfs->ops->context_valid_write)
return -EOPNOTSUPP;
mutex_lock(&ptm_debugfs->lock);
ret = ptm_debugfs->ops->context_valid_write(ptm_debugfs->pdata, !!val);
mutex_unlock(&ptm_debugfs->lock);
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(context_valid_fops, context_valid_get,
context_valid_set, "%llu\n");
static int local_clock_get(void *data, u64 *val)
{
struct pci_ptm_debugfs *ptm_debugfs = data;
u64 clock;
int ret;
if (!ptm_debugfs->ops->local_clock_read)
return -EOPNOTSUPP;
ret = ptm_debugfs->ops->local_clock_read(ptm_debugfs->pdata, &clock);
if (ret)
return ret;
*val = clock;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(local_clock_fops, local_clock_get, NULL, "%llu\n");
static int master_clock_get(void *data, u64 *val)
{
struct pci_ptm_debugfs *ptm_debugfs = data;
u64 clock;
int ret;
if (!ptm_debugfs->ops->master_clock_read)
return -EOPNOTSUPP;
ret = ptm_debugfs->ops->master_clock_read(ptm_debugfs->pdata, &clock);
if (ret)
return ret;
*val = clock;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(master_clock_fops, master_clock_get, NULL, "%llu\n");
static int t1_get(void *data, u64 *val)
{
struct pci_ptm_debugfs *ptm_debugfs = data;
u64 clock;
int ret;
if (!ptm_debugfs->ops->t1_read)
return -EOPNOTSUPP;
ret = ptm_debugfs->ops->t1_read(ptm_debugfs->pdata, &clock);
if (ret)
return ret;
*val = clock;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(t1_fops, t1_get, NULL, "%llu\n");
static int t2_get(void *data, u64 *val)
{
struct pci_ptm_debugfs *ptm_debugfs = data;
u64 clock;
int ret;
if (!ptm_debugfs->ops->t2_read)
return -EOPNOTSUPP;
ret = ptm_debugfs->ops->t2_read(ptm_debugfs->pdata, &clock);
if (ret)
return ret;
*val = clock;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(t2_fops, t2_get, NULL, "%llu\n");
static int t3_get(void *data, u64 *val)
{
struct pci_ptm_debugfs *ptm_debugfs = data;
u64 clock;
int ret;
if (!ptm_debugfs->ops->t3_read)
return -EOPNOTSUPP;
ret = ptm_debugfs->ops->t3_read(ptm_debugfs->pdata, &clock);
if (ret)
return ret;
*val = clock;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(t3_fops, t3_get, NULL, "%llu\n");
static int t4_get(void *data, u64 *val)
{
struct pci_ptm_debugfs *ptm_debugfs = data;
u64 clock;
int ret;
if (!ptm_debugfs->ops->t4_read)
return -EOPNOTSUPP;
ret = ptm_debugfs->ops->t4_read(ptm_debugfs->pdata, &clock);
if (ret)
return ret;
*val = clock;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(t4_fops, t4_get, NULL, "%llu\n");
#define pcie_ptm_create_debugfs_file(pdata, mode, attr) \
do { \
if (ops->attr##_visible && ops->attr##_visible(pdata)) \
debugfs_create_file(#attr, mode, ptm_debugfs->debugfs, \
ptm_debugfs, &attr##_fops); \
} while (0)
/*
* pcie_ptm_create_debugfs() - Create debugfs entries for the PTM context
* @dev: PTM capable component device
* @pdata: Private data of the PTM capable component device
* @ops: PTM callback structure
*
* Create debugfs entries for exposing the PTM context of the PTM capable
* components such as Root Complex and Endpoint controllers.
*
* Return: Pointer to 'struct pci_ptm_debugfs' if success, NULL otherwise.
*/
struct pci_ptm_debugfs *pcie_ptm_create_debugfs(struct device *dev, void *pdata,
const struct pcie_ptm_ops *ops)
{
struct pci_ptm_debugfs *ptm_debugfs;
char *dirname;
int ret;
/* Caller must provide check_capability() callback */
if (!ops->check_capability)
return NULL;
/* Check for PTM capability before creating debugfs attributes */
ret = ops->check_capability(pdata);
if (!ret) {
dev_dbg(dev, "PTM capability not present\n");
return NULL;
}
ptm_debugfs = kzalloc_obj(*ptm_debugfs);
if (!ptm_debugfs)
return NULL;
dirname = devm_kasprintf(dev, GFP_KERNEL, "pcie_ptm_%s", dev_name(dev));
if (!dirname) {
kfree(ptm_debugfs);
return NULL;
}
ptm_debugfs->debugfs = debugfs_create_dir(dirname, NULL);
ptm_debugfs->pdata = pdata;
ptm_debugfs->ops = ops;
mutex_init(&ptm_debugfs->lock);
pcie_ptm_create_debugfs_file(pdata, 0644, context_update);
pcie_ptm_create_debugfs_file(pdata, 0644, context_valid);
pcie_ptm_create_debugfs_file(pdata, 0444, local_clock);
pcie_ptm_create_debugfs_file(pdata, 0444, master_clock);
pcie_ptm_create_debugfs_file(pdata, 0444, t1);
pcie_ptm_create_debugfs_file(pdata, 0444, t2);
pcie_ptm_create_debugfs_file(pdata, 0444, t3);
pcie_ptm_create_debugfs_file(pdata, 0444, t4);
return ptm_debugfs;
}
EXPORT_SYMBOL_GPL(pcie_ptm_create_debugfs);
/*
* pcie_ptm_destroy_debugfs() - Destroy debugfs entries for the PTM context
* @ptm_debugfs: Pointer to the PTM debugfs struct
*/
void pcie_ptm_destroy_debugfs(struct pci_ptm_debugfs *ptm_debugfs)
{
if (!ptm_debugfs)
return;
mutex_destroy(&ptm_debugfs->lock);
debugfs_remove_recursive(ptm_debugfs->debugfs);
kfree(ptm_debugfs);
}
EXPORT_SYMBOL_GPL(pcie_ptm_destroy_debugfs);
#endif
+190
View File
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Root Complex Event Collector Support
*
* Authors:
* Sean V Kelley <sean.v.kelley@intel.com>
* Qiuxu Zhuo <qiuxu.zhuo@intel.com>
*
* Copyright (C) 2020 Intel Corp.
*/
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/pci_regs.h>
#include "../pci.h"
struct walk_rcec_data {
struct pci_dev *rcec;
int (*user_callback)(struct pci_dev *dev, void *data);
void *user_data;
};
static bool rcec_assoc_rciep(struct pci_dev *rcec, struct pci_dev *rciep)
{
unsigned long bitmap = rcec->rcec_ea->bitmap;
unsigned int devn;
/* An RCiEP found on a different bus in range */
if (rcec->bus->number != rciep->bus->number)
return true;
/* Same bus, so check bitmap */
for_each_set_bit(devn, &bitmap, 32)
if (devn == PCI_SLOT(rciep->devfn))
return true;
return false;
}
static int link_rcec_helper(struct pci_dev *dev, void *data)
{
struct walk_rcec_data *rcec_data = data;
struct pci_dev *rcec = rcec_data->rcec;
if ((pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END) &&
rcec_assoc_rciep(rcec, dev)) {
dev->rcec = rcec;
pci_dbg(dev, "PME & error events signaled via %s\n",
pci_name(rcec));
}
return 0;
}
static int walk_rcec_helper(struct pci_dev *dev, void *data)
{
struct walk_rcec_data *rcec_data = data;
struct pci_dev *rcec = rcec_data->rcec;
if ((pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END) &&
rcec_assoc_rciep(rcec, dev))
rcec_data->user_callback(dev, rcec_data->user_data);
return 0;
}
static void walk_rcec(int (*cb)(struct pci_dev *dev, void *data),
void *userdata)
{
struct walk_rcec_data *rcec_data = userdata;
struct pci_dev *rcec = rcec_data->rcec;
u8 nextbusn, lastbusn;
struct pci_bus *bus;
unsigned int bnr;
if (!rcec->rcec_ea)
return;
/* Walk own bus for bitmap based association */
pci_walk_bus(rcec->bus, cb, rcec_data);
nextbusn = rcec->rcec_ea->nextbusn;
lastbusn = rcec->rcec_ea->lastbusn;
/* All RCiEP devices are on the same bus as the RCEC */
if (nextbusn == 0xff && lastbusn == 0x00)
return;
for (bnr = nextbusn; bnr <= lastbusn; bnr++) {
/* No association indicated (PCIe 5.0-1, 7.9.10.3) */
if (bnr == rcec->bus->number)
continue;
bus = pci_find_bus(pci_domain_nr(rcec->bus), bnr);
if (!bus)
continue;
/* Find RCiEP devices on the given bus ranges */
pci_walk_bus(bus, cb, rcec_data);
}
}
/**
* pcie_link_rcec - Link RCiEP devices associated with RCEC.
* @rcec: RCEC whose RCiEP devices should be linked.
*
* Link the given RCEC to each RCiEP device found.
*/
void pcie_link_rcec(struct pci_dev *rcec)
{
struct walk_rcec_data rcec_data;
if (!rcec->rcec_ea)
return;
rcec_data.rcec = rcec;
rcec_data.user_callback = NULL;
rcec_data.user_data = NULL;
walk_rcec(link_rcec_helper, &rcec_data);
}
/**
* pcie_walk_rcec - Walk RCiEP devices associating with RCEC and call callback.
* @rcec: RCEC whose RCiEP devices should be walked
* @cb: Callback to be called for each RCiEP device found
* @userdata: Arbitrary pointer to be passed to callback
*
* Walk the given RCEC. Call the callback on each RCiEP found.
*
* If @cb returns anything other than 0, break out.
*/
void pcie_walk_rcec(struct pci_dev *rcec, int (*cb)(struct pci_dev *, void *),
void *userdata)
{
struct walk_rcec_data rcec_data;
if (!rcec->rcec_ea)
return;
rcec_data.rcec = rcec;
rcec_data.user_callback = cb;
rcec_data.user_data = userdata;
walk_rcec(walk_rcec_helper, &rcec_data);
}
void pci_rcec_init(struct pci_dev *dev)
{
struct rcec_ea *rcec_ea;
u32 rcec, hdr, busn;
u8 ver;
/* Only for Root Complex Event Collectors */
if (pci_pcie_type(dev) != PCI_EXP_TYPE_RC_EC)
return;
rcec = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_RCEC);
if (!rcec)
return;
rcec_ea = kzalloc_obj(*rcec_ea);
if (!rcec_ea)
return;
pci_read_config_dword(dev, rcec + PCI_RCEC_RCIEP_BITMAP,
&rcec_ea->bitmap);
/* Check whether RCEC BUSN register is present */
pci_read_config_dword(dev, rcec, &hdr);
ver = PCI_EXT_CAP_VER(hdr);
if (ver >= PCI_RCEC_BUSN_REG_VER) {
pci_read_config_dword(dev, rcec + PCI_RCEC_BUSN, &busn);
rcec_ea->nextbusn = PCI_RCEC_BUSN_NEXT(busn);
rcec_ea->lastbusn = PCI_RCEC_BUSN_LAST(busn);
} else {
/* Avoid later ver check by setting nextbusn */
rcec_ea->nextbusn = 0xff;
rcec_ea->lastbusn = 0x00;
}
dev->rcec_ea = rcec_ea;
}
void pci_rcec_exit(struct pci_dev *dev)
{
kfree(dev->rcec_ea);
dev->rcec_ea = NULL;
}
+137
View File
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCIe TLP Log handling
*
* Copyright (C) 2024 Intel Corporation
*/
#include <linux/aer.h>
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/pci.h>
#include <linux/string.h>
#include "../pci.h"
/**
* aer_tlp_log_len - Calculate AER Capability TLP Header/Prefix Log length
* @dev: PCIe device
* @aercc: AER Capabilities and Control register value
*
* Return: TLP Header/Prefix Log length
*/
unsigned int aer_tlp_log_len(struct pci_dev *dev, u32 aercc)
{
if (aercc & PCI_ERR_CAP_TLP_LOG_FLIT)
return FIELD_GET(PCI_ERR_CAP_TLP_LOG_SIZE, aercc);
return PCIE_STD_NUM_TLP_HEADERLOG +
((aercc & PCI_ERR_CAP_PREFIX_LOG_PRESENT) ?
dev->eetlp_prefix_max : 0);
}
#ifdef CONFIG_PCIE_DPC
/**
* dpc_tlp_log_len - Calculate DPC RP PIO TLP Header/Prefix Log length
* @dev: PCIe device
*
* Return: TLP Header/Prefix Log length
*/
unsigned int dpc_tlp_log_len(struct pci_dev *dev)
{
/* Remove ImpSpec Log register from the count */
if (dev->dpc_rp_log_size >= PCIE_STD_NUM_TLP_HEADERLOG + 1)
return dev->dpc_rp_log_size - 1;
return dev->dpc_rp_log_size;
}
#endif
/**
* pcie_read_tlp_log - read TLP Header Log
* @dev: PCIe device
* @where: PCI Config offset of TLP Header Log
* @where2: PCI Config offset of TLP Prefix Log
* @tlp_len: TLP Log length (Header Log + TLP Prefix Log in DWORDs)
* @flit: TLP Logged in Flit mode
* @log: TLP Log structure to fill
*
* Fill @log from TLP Header Log registers, e.g., AER or DPC.
*
* Return: 0 on success and filled TLP Log structure, <0 on error.
*/
int pcie_read_tlp_log(struct pci_dev *dev, int where, int where2,
unsigned int tlp_len, bool flit, struct pcie_tlp_log *log)
{
unsigned int i;
int off, ret;
if (tlp_len > ARRAY_SIZE(log->dw))
tlp_len = ARRAY_SIZE(log->dw);
memset(log, 0, sizeof(*log));
for (i = 0; i < tlp_len; i++) {
if (i < PCIE_STD_NUM_TLP_HEADERLOG)
off = where + i * 4;
else
off = where2 + (i - PCIE_STD_NUM_TLP_HEADERLOG) * 4;
ret = pci_read_config_dword(dev, off, &log->dw[i]);
if (ret)
return pcibios_err_to_errno(ret);
}
/*
* Hard-code non-Flit mode to 4 DWORDs, for now. The exact length
* can only be known if the TLP is parsed.
*/
log->header_len = flit ? tlp_len : 4;
log->flit = flit;
return 0;
}
#define EE_PREFIX_STR " E-E Prefixes:"
/**
* pcie_print_tlp_log - Print TLP Header / Prefix Log contents
* @dev: PCIe device
* @log: TLP Log structure
* @level: Printk log level
* @pfx: String prefix
*
* Prints TLP Header and Prefix Log information held by @log.
*/
void pcie_print_tlp_log(const struct pci_dev *dev,
const struct pcie_tlp_log *log, const char *level,
const char *pfx)
{
/* EE_PREFIX_STR fits the extended DW space needed for the Flit mode */
char buf[11 * PCIE_STD_MAX_TLP_HEADERLOG + 1];
unsigned int i;
int len;
len = scnprintf(buf, sizeof(buf), "%#010x %#010x %#010x %#010x",
log->dw[0], log->dw[1], log->dw[2], log->dw[3]);
if (log->flit) {
for (i = PCIE_STD_NUM_TLP_HEADERLOG; i < log->header_len; i++) {
len += scnprintf(buf + len, sizeof(buf) - len,
" %#010x", log->dw[i]);
}
} else {
if (log->prefix[0])
len += scnprintf(buf + len, sizeof(buf) - len,
EE_PREFIX_STR);
for (i = 0; i < ARRAY_SIZE(log->prefix); i++) {
if (!log->prefix[i])
break;
len += scnprintf(buf + len, sizeof(buf) - len,
" %#010x", log->prefix[i]);
}
}
dev_printk(level, &dev->dev, "%sTLP Header%s: %s\n", pfx,
log->flit ? " (Flit)" : "", buf);
}