forked from Minki/linux
Merge branch 'pci/err'
- Stop writing AER Capability when we don't own it (Sean V Kelley) - Bind RCEC devices to the Port driver (Qiuxu Zhuo) - Cache the RCEC RA Capability offset (Sean V Kelley) - Add pci_walk_bridge() (Sean V Kelley) - Clear AER status only when we control AER (Sean V Kelley) - Recover from RCEC AER errors (Sean V Kelley) - Add pcie_link_rcec() to associate RCiEPs with RCECs (Sean V Kelley) - Recover from RCiEP AER errors (Sean V Kelley) - Add pcie_walk_rcec() for RCEC AER handling (Sean V Kelley) - Add pcie_walk_rcec() for RCEC PME handling (Sean V Kelley) - Add RCEC AER error injection support (Qiuxu Zhuo) * pci/err: PCI/AER: Add RCEC AER error injection support PCI/PME: Add pcie_walk_rcec() to RCEC PME handling PCI/AER: Add pcie_walk_rcec() to RCEC AER handling PCI/ERR: Recover from RCiEP AER errors PCI/ERR: Add pcie_link_rcec() to associate RCiEPs PCI/ERR: Recover from RCEC AER errors PCI/ERR: Clear AER status only when we control AER PCI/ERR: Add pci_walk_bridge() to pcie_do_recovery() PCI/ERR: Avoid negated conditional for clarity PCI/ERR: Use "bridge" for clarity in pcie_do_recovery() PCI/ERR: Simplify by computing pci_pcie_type() once PCI/ERR: Simplify by using pci_upstream_bridge() PCI/ERR: Rename reset_link() to reset_subordinates() PCI/ERR: Cache RCEC EA Capability offset in pci_init_capabilities() PCI/ERR: Bind RCEC devices to the Root Port driver PCI/AER: Write AER Capability only when we control it
This commit is contained in:
commit
6a94785fb9
@ -450,6 +450,15 @@ int aer_get_device_error_info(struct pci_dev *dev, struct aer_err_info *info);
|
||||
void aer_print_error(struct pci_dev *dev, struct aer_err_info *info);
|
||||
#endif /* CONFIG_PCIEAER */
|
||||
|
||||
#ifdef CONFIG_PCIEPORTBUS
|
||||
/* Cached RCEC Endpoint Association */
|
||||
struct rcec_ea {
|
||||
u8 nextbusn;
|
||||
u8 lastbusn;
|
||||
u32 bitmap;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PCIE_DPC
|
||||
void pci_save_dpc_state(struct pci_dev *dev);
|
||||
void pci_restore_dpc_state(struct pci_dev *dev);
|
||||
@ -462,6 +471,22 @@ static inline void pci_restore_dpc_state(struct pci_dev *dev) {}
|
||||
static inline void pci_dpc_init(struct pci_dev *pdev) {}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PCIEPORTBUS
|
||||
void pci_rcec_init(struct pci_dev *dev);
|
||||
void pci_rcec_exit(struct pci_dev *dev);
|
||||
void pcie_link_rcec(struct pci_dev *rcec);
|
||||
void pcie_walk_rcec(struct pci_dev *rcec,
|
||||
int (*cb)(struct pci_dev *, void *),
|
||||
void *userdata);
|
||||
#else
|
||||
static inline void pci_rcec_init(struct pci_dev *dev) {}
|
||||
static inline void pci_rcec_exit(struct pci_dev *dev) {}
|
||||
static inline void pcie_link_rcec(struct pci_dev *rcec) {}
|
||||
static inline void pcie_walk_rcec(struct pci_dev *rcec,
|
||||
int (*cb)(struct pci_dev *, void *),
|
||||
void *userdata) {}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PCI_ATS
|
||||
/* Address Translation Service */
|
||||
void pci_ats_init(struct pci_dev *dev);
|
||||
@ -557,8 +582,8 @@ static inline int pci_dev_specific_disable_acs_redir(struct pci_dev *dev)
|
||||
|
||||
/* PCI error reporting and recovery */
|
||||
pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
|
||||
pci_channel_state_t state,
|
||||
pci_ers_result_t (*reset_link)(struct pci_dev *pdev));
|
||||
pci_channel_state_t state,
|
||||
pci_ers_result_t (*reset_subordinates)(struct pci_dev *pdev));
|
||||
|
||||
bool pcie_wait_for_link(struct pci_dev *pdev, bool active);
|
||||
#ifdef CONFIG_PCIEASPM
|
||||
|
@ -2,7 +2,7 @@
|
||||
#
|
||||
# Makefile for PCI Express features and port driver
|
||||
|
||||
pcieportdrv-y := portdrv_core.o portdrv_pci.o err.o
|
||||
pcieportdrv-y := portdrv_core.o portdrv_pci.o err.o rcec.o
|
||||
|
||||
obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o
|
||||
|
||||
|
@ -300,7 +300,8 @@ int pci_aer_raw_clear_status(struct pci_dev *dev)
|
||||
return -EIO;
|
||||
|
||||
port_type = pci_pcie_type(dev);
|
||||
if (port_type == PCI_EXP_TYPE_ROOT_PORT) {
|
||||
if (port_type == PCI_EXP_TYPE_ROOT_PORT ||
|
||||
port_type == PCI_EXP_TYPE_RC_EC) {
|
||||
pci_read_config_dword(dev, aer + PCI_ERR_ROOT_STATUS, &status);
|
||||
pci_write_config_dword(dev, aer + PCI_ERR_ROOT_STATUS, status);
|
||||
}
|
||||
@ -595,7 +596,8 @@ static umode_t aer_stats_attrs_are_visible(struct kobject *kobj,
|
||||
if ((a == &dev_attr_aer_rootport_total_err_cor.attr ||
|
||||
a == &dev_attr_aer_rootport_total_err_fatal.attr ||
|
||||
a == &dev_attr_aer_rootport_total_err_nonfatal.attr) &&
|
||||
pci_pcie_type(pdev) != PCI_EXP_TYPE_ROOT_PORT)
|
||||
((pci_pcie_type(pdev) != PCI_EXP_TYPE_ROOT_PORT) &&
|
||||
(pci_pcie_type(pdev) != PCI_EXP_TYPE_RC_EC)))
|
||||
return 0;
|
||||
|
||||
return a->mode;
|
||||
@ -916,7 +918,10 @@ static bool find_source_device(struct pci_dev *parent,
|
||||
if (result)
|
||||
return true;
|
||||
|
||||
pci_walk_bus(parent->subordinate, find_device_iter, e_info);
|
||||
if (pci_pcie_type(parent) == PCI_EXP_TYPE_RC_EC)
|
||||
pcie_walk_rcec(parent, find_device_iter, e_info);
|
||||
else
|
||||
pci_walk_bus(parent->subordinate, find_device_iter, e_info);
|
||||
|
||||
if (!e_info->error_dev_num) {
|
||||
pci_info(parent, "can't find device of ID%04x\n", e_info->id);
|
||||
@ -1034,6 +1039,7 @@ EXPORT_SYMBOL_GPL(aer_recover_queue);
|
||||
*/
|
||||
int aer_get_device_error_info(struct pci_dev *dev, struct aer_err_info *info)
|
||||
{
|
||||
int type = pci_pcie_type(dev);
|
||||
int aer = dev->aer_cap;
|
||||
int temp;
|
||||
|
||||
@ -1052,8 +1058,9 @@ int aer_get_device_error_info(struct pci_dev *dev, struct aer_err_info *info)
|
||||
&info->mask);
|
||||
if (!(info->status & ~info->mask))
|
||||
return 0;
|
||||
} else if (pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT ||
|
||||
pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM ||
|
||||
} else if (type == PCI_EXP_TYPE_ROOT_PORT ||
|
||||
type == PCI_EXP_TYPE_RC_EC ||
|
||||
type == PCI_EXP_TYPE_DOWNSTREAM ||
|
||||
info->severity == AER_NONFATAL) {
|
||||
|
||||
/* Link is still healthy for IO reads */
|
||||
@ -1205,6 +1212,7 @@ static int set_device_error_reporting(struct pci_dev *dev, void *data)
|
||||
int type = pci_pcie_type(dev);
|
||||
|
||||
if ((type == PCI_EXP_TYPE_ROOT_PORT) ||
|
||||
(type == PCI_EXP_TYPE_RC_EC) ||
|
||||
(type == PCI_EXP_TYPE_UPSTREAM) ||
|
||||
(type == PCI_EXP_TYPE_DOWNSTREAM)) {
|
||||
if (enable)
|
||||
@ -1229,9 +1237,12 @@ static void set_downstream_devices_error_reporting(struct pci_dev *dev,
|
||||
{
|
||||
set_device_error_reporting(dev, &enable);
|
||||
|
||||
if (!dev->subordinate)
|
||||
return;
|
||||
pci_walk_bus(dev->subordinate, set_device_error_reporting, &enable);
|
||||
if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_EC)
|
||||
pcie_walk_rcec(dev, set_device_error_reporting, &enable);
|
||||
else if (dev->subordinate)
|
||||
pci_walk_bus(dev->subordinate, set_device_error_reporting,
|
||||
&enable);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1329,6 +1340,11 @@ static int aer_probe(struct pcie_device *dev)
|
||||
struct device *device = &dev->device;
|
||||
struct pci_dev *port = dev->port;
|
||||
|
||||
/* Limit to Root Ports or Root Complex Event Collectors */
|
||||
if ((pci_pcie_type(port) != PCI_EXP_TYPE_RC_EC) &&
|
||||
(pci_pcie_type(port) != PCI_EXP_TYPE_ROOT_PORT))
|
||||
return -ENODEV;
|
||||
|
||||
rpc = devm_kzalloc(device, sizeof(struct aer_rpc), GFP_KERNEL);
|
||||
if (!rpc)
|
||||
return -ENOMEM;
|
||||
@ -1350,41 +1366,74 @@ static int aer_probe(struct pcie_device *dev)
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_root_reset - reset link on Root Port
|
||||
* @dev: pointer to Root Port's pci_dev data structure
|
||||
* aer_root_reset - reset Root Port hierarchy, RCEC, or RCiEP
|
||||
* @dev: pointer to Root Port, RCEC, or RCiEP
|
||||
*
|
||||
* Invoked by Port Bus driver when performing link reset at Root Port.
|
||||
* Invoked by Port Bus driver when performing reset.
|
||||
*/
|
||||
static pci_ers_result_t aer_root_reset(struct pci_dev *dev)
|
||||
{
|
||||
int aer = dev->aer_cap;
|
||||
int type = pci_pcie_type(dev);
|
||||
struct pci_dev *root;
|
||||
int aer;
|
||||
struct pci_host_bridge *host = pci_find_host_bridge(dev->bus);
|
||||
u32 reg32;
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* Only Root Ports and RCECs have AER Root Command and Root Status
|
||||
* registers. If "dev" is an RCiEP, the relevant registers are in
|
||||
* the RCEC.
|
||||
*/
|
||||
if (type == PCI_EXP_TYPE_RC_END)
|
||||
root = dev->rcec;
|
||||
else
|
||||
root = dev;
|
||||
|
||||
/* Disable Root's interrupt in response to error messages */
|
||||
pci_read_config_dword(dev, aer + PCI_ERR_ROOT_COMMAND, ®32);
|
||||
reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK;
|
||||
pci_write_config_dword(dev, aer + PCI_ERR_ROOT_COMMAND, reg32);
|
||||
/*
|
||||
* If the platform retained control of AER, an RCiEP may not have
|
||||
* an RCEC visible to us, so dev->rcec ("root") may be NULL. In
|
||||
* that case, firmware is responsible for these registers.
|
||||
*/
|
||||
aer = root ? root->aer_cap : 0;
|
||||
|
||||
rc = pci_bus_error_reset(dev);
|
||||
pci_info(dev, "Root Port link has been reset\n");
|
||||
if ((host->native_aer || pcie_ports_native) && aer) {
|
||||
/* Disable Root's interrupt in response to error messages */
|
||||
pci_read_config_dword(root, aer + PCI_ERR_ROOT_COMMAND, ®32);
|
||||
reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK;
|
||||
pci_write_config_dword(root, aer + PCI_ERR_ROOT_COMMAND, reg32);
|
||||
}
|
||||
|
||||
/* Clear Root Error Status */
|
||||
pci_read_config_dword(dev, aer + PCI_ERR_ROOT_STATUS, ®32);
|
||||
pci_write_config_dword(dev, aer + PCI_ERR_ROOT_STATUS, reg32);
|
||||
if (type == PCI_EXP_TYPE_RC_EC || type == PCI_EXP_TYPE_RC_END) {
|
||||
if (pcie_has_flr(dev)) {
|
||||
rc = pcie_flr(dev);
|
||||
pci_info(dev, "has been reset (%d)\n", rc);
|
||||
} else {
|
||||
pci_info(dev, "not reset (no FLR support)\n");
|
||||
rc = -ENOTTY;
|
||||
}
|
||||
} else {
|
||||
rc = pci_bus_error_reset(dev);
|
||||
pci_info(dev, "Root Port link has been reset (%d)\n", rc);
|
||||
}
|
||||
|
||||
/* Enable Root Port's interrupt in response to error messages */
|
||||
pci_read_config_dword(dev, aer + PCI_ERR_ROOT_COMMAND, ®32);
|
||||
reg32 |= ROOT_PORT_INTR_ON_MESG_MASK;
|
||||
pci_write_config_dword(dev, aer + PCI_ERR_ROOT_COMMAND, reg32);
|
||||
if ((host->native_aer || pcie_ports_native) && aer) {
|
||||
/* Clear Root Error Status */
|
||||
pci_read_config_dword(root, aer + PCI_ERR_ROOT_STATUS, ®32);
|
||||
pci_write_config_dword(root, aer + PCI_ERR_ROOT_STATUS, reg32);
|
||||
|
||||
/* Enable Root Port's interrupt in response to error messages */
|
||||
pci_read_config_dword(root, aer + PCI_ERR_ROOT_COMMAND, ®32);
|
||||
reg32 |= ROOT_PORT_INTR_ON_MESG_MASK;
|
||||
pci_write_config_dword(root, aer + PCI_ERR_ROOT_COMMAND, reg32);
|
||||
}
|
||||
|
||||
return rc ? PCI_ERS_RESULT_DISCONNECT : PCI_ERS_RESULT_RECOVERED;
|
||||
}
|
||||
|
||||
static struct pcie_port_service_driver aerdriver = {
|
||||
.name = "aer",
|
||||
.port_type = PCI_EXP_TYPE_ROOT_PORT,
|
||||
.port_type = PCIE_ANY_PORT,
|
||||
.service = PCIE_PORT_SERVICE_AER,
|
||||
|
||||
.probe = aer_probe,
|
||||
|
@ -333,8 +333,11 @@ static int aer_inject(struct aer_error_inj *einj)
|
||||
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, "Root port not found\n");
|
||||
pci_err(dev, "Neither Root Port nor RCEC found\n");
|
||||
ret = -ENODEV;
|
||||
goto out_put;
|
||||
}
|
||||
|
@ -146,38 +146,71 @@ out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
|
||||
pci_channel_state_t state,
|
||||
pci_ers_result_t (*reset_link)(struct pci_dev *pdev))
|
||||
/**
|
||||
* 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_bus *bus;
|
||||
struct pci_host_bridge *host = pci_find_host_bridge(dev->bus);
|
||||
|
||||
/*
|
||||
* Error recovery runs on all subordinates of the first downstream port.
|
||||
* If the downstream port detected the error, it is cleared at the end.
|
||||
* 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 (!(pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT ||
|
||||
pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM))
|
||||
dev = dev->bus->self;
|
||||
bus = dev->subordinate;
|
||||
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_dbg(dev, "broadcast error_detected message\n");
|
||||
pci_dbg(bridge, "broadcast error_detected message\n");
|
||||
if (state == pci_channel_io_frozen) {
|
||||
pci_walk_bus(bus, report_frozen_detected, &status);
|
||||
status = reset_link(dev);
|
||||
pci_walk_bridge(bridge, report_frozen_detected, &status);
|
||||
status = reset_subordinates(bridge);
|
||||
if (status != PCI_ERS_RESULT_RECOVERED) {
|
||||
pci_warn(dev, "link reset failed\n");
|
||||
pci_warn(bridge, "subordinate device reset failed\n");
|
||||
goto failed;
|
||||
}
|
||||
} else {
|
||||
pci_walk_bus(bus, report_normal_detected, &status);
|
||||
pci_walk_bridge(bridge, report_normal_detected, &status);
|
||||
}
|
||||
|
||||
if (status == PCI_ERS_RESULT_CAN_RECOVER) {
|
||||
status = PCI_ERS_RESULT_RECOVERED;
|
||||
pci_dbg(dev, "broadcast mmio_enabled message\n");
|
||||
pci_walk_bus(bus, report_mmio_enabled, &status);
|
||||
pci_dbg(bridge, "broadcast mmio_enabled message\n");
|
||||
pci_walk_bridge(bridge, report_mmio_enabled, &status);
|
||||
}
|
||||
|
||||
if (status == PCI_ERS_RESULT_NEED_RESET) {
|
||||
@ -187,27 +220,35 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
|
||||
* drivers' slot_reset callbacks?
|
||||
*/
|
||||
status = PCI_ERS_RESULT_RECOVERED;
|
||||
pci_dbg(dev, "broadcast slot_reset message\n");
|
||||
pci_walk_bus(bus, report_slot_reset, &status);
|
||||
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(dev, "broadcast resume message\n");
|
||||
pci_walk_bus(bus, report_resume, &status);
|
||||
pci_dbg(bridge, "broadcast resume message\n");
|
||||
pci_walk_bridge(bridge, report_resume, &status);
|
||||
|
||||
if (pcie_aer_is_native(dev))
|
||||
pcie_clear_device_status(dev);
|
||||
pci_aer_clear_nonfatal_status(dev);
|
||||
pci_info(dev, "device recovery successful\n");
|
||||
/*
|
||||
* If we have native control of AER, clear error status in the Root
|
||||
* Port or Downstream Port that signaled 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(bridge);
|
||||
pci_aer_clear_nonfatal_status(bridge);
|
||||
}
|
||||
pci_info(bridge, "device recovery successful\n");
|
||||
return status;
|
||||
|
||||
failed:
|
||||
pci_uevent_ers(dev, PCI_ERS_RESULT_DISCONNECT);
|
||||
pci_uevent_ers(bridge, PCI_ERS_RESULT_DISCONNECT);
|
||||
|
||||
/* TODO: Should kernel panic here? */
|
||||
pci_info(dev, "device recovery failed\n");
|
||||
pci_info(bridge, "device recovery failed\n");
|
||||
|
||||
return status;
|
||||
}
|
||||
|
@ -310,7 +310,10 @@ static int pcie_pme_can_wakeup(struct pci_dev *dev, void *ign)
|
||||
static void pcie_pme_mark_devices(struct pci_dev *port)
|
||||
{
|
||||
pcie_pme_can_wakeup(port, NULL);
|
||||
if (port->subordinate)
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -320,10 +323,16 @@ static void pcie_pme_mark_devices(struct pci_dev *port)
|
||||
*/
|
||||
static int pcie_pme_probe(struct pcie_device *srv)
|
||||
{
|
||||
struct pci_dev *port;
|
||||
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(sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
@ -333,7 +342,6 @@ static int pcie_pme_probe(struct pcie_device *srv)
|
||||
data->srv = srv;
|
||||
set_service_data(srv, data);
|
||||
|
||||
port = srv->port;
|
||||
pcie_pme_interrupt_enable(port, false);
|
||||
pcie_clear_root_pme_status(port);
|
||||
|
||||
@ -445,7 +453,7 @@ static void pcie_pme_remove(struct pcie_device *srv)
|
||||
|
||||
static struct pcie_port_service_driver pcie_pme_driver = {
|
||||
.name = "pcie_pme",
|
||||
.port_type = PCI_EXP_TYPE_ROOT_PORT,
|
||||
.port_type = PCIE_ANY_PORT,
|
||||
.service = PCIE_PORT_SERVICE_PME,
|
||||
|
||||
.probe = pcie_pme_probe,
|
||||
|
@ -233,12 +233,9 @@ static int get_port_device_capability(struct pci_dev *dev)
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Root ports are capable of generating PME too. Root Complex
|
||||
* Event Collectors can also generate PMEs, but we don't handle
|
||||
* those yet.
|
||||
*/
|
||||
if (pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT &&
|
||||
/* 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;
|
||||
|
||||
|
@ -101,14 +101,19 @@ static const struct dev_pm_ops pcie_portdrv_pm_ops = {
|
||||
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) ||
|
||||
((pci_pcie_type(dev) != PCI_EXP_TYPE_ROOT_PORT) &&
|
||||
(pci_pcie_type(dev) != PCI_EXP_TYPE_UPSTREAM) &&
|
||||
(pci_pcie_type(dev) != PCI_EXP_TYPE_DOWNSTREAM)))
|
||||
((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;
|
||||
@ -195,6 +200,8 @@ static const struct pci_device_id port_pci_ids[] = {
|
||||
{ PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), ~0) },
|
||||
/* subtractive decode PCI-to-PCI bridge, class type is 060401h */
|
||||
{ PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x01), ~0) },
|
||||
/* handle any Root Complex Event Collector */
|
||||
{ PCI_DEVICE_CLASS(((PCI_CLASS_SYSTEM_RCEC << 8) | 0x00), ~0) },
|
||||
{ },
|
||||
};
|
||||
|
||||
|
190
drivers/pci/pcie/rcec.c
Normal file
190
drivers/pci/pcie/rcec.c
Normal 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 == 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(sizeof(*rcec_ea), GFP_KERNEL);
|
||||
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;
|
||||
}
|
@ -2217,6 +2217,7 @@ static void pci_configure_device(struct pci_dev *dev)
|
||||
static void pci_release_capabilities(struct pci_dev *dev)
|
||||
{
|
||||
pci_aer_exit(dev);
|
||||
pci_rcec_exit(dev);
|
||||
pci_vpd_release(dev);
|
||||
pci_iov_release(dev);
|
||||
pci_free_cap_save_buffers(dev);
|
||||
@ -2416,6 +2417,7 @@ static void pci_init_capabilities(struct pci_dev *dev)
|
||||
pci_ptm_init(dev); /* Precision Time Measurement */
|
||||
pci_aer_init(dev); /* Advanced Error Reporting */
|
||||
pci_dpc_init(dev); /* Downstream Port Containment */
|
||||
pci_rcec_init(dev); /* Root Complex Event Collector */
|
||||
|
||||
pcie_report_downtraining(dev);
|
||||
|
||||
|
@ -305,6 +305,7 @@ struct pcie_link_state;
|
||||
struct pci_vpd;
|
||||
struct pci_sriov;
|
||||
struct pci_p2pdma;
|
||||
struct rcec_ea;
|
||||
|
||||
/* The pci_dev structure describes PCI devices */
|
||||
struct pci_dev {
|
||||
@ -327,6 +328,10 @@ struct pci_dev {
|
||||
#ifdef CONFIG_PCIEAER
|
||||
u16 aer_cap; /* AER capability offset */
|
||||
struct aer_stats *aer_stats; /* AER stats for this device */
|
||||
#endif
|
||||
#ifdef CONFIG_PCIEPORTBUS
|
||||
struct rcec_ea *rcec_ea; /* RCEC cached endpoint association */
|
||||
struct pci_dev *rcec; /* Associated RCEC device */
|
||||
#endif
|
||||
u8 pcie_cap; /* PCIe capability offset */
|
||||
u8 msi_cap; /* MSI capability offset */
|
||||
|
@ -81,6 +81,7 @@
|
||||
#define PCI_CLASS_SYSTEM_RTC 0x0803
|
||||
#define PCI_CLASS_SYSTEM_PCI_HOTPLUG 0x0804
|
||||
#define PCI_CLASS_SYSTEM_SDHCI 0x0805
|
||||
#define PCI_CLASS_SYSTEM_RCEC 0x0807
|
||||
#define PCI_CLASS_SYSTEM_OTHER 0x0880
|
||||
|
||||
#define PCI_BASE_CLASS_INPUT 0x09
|
||||
|
@ -835,6 +835,13 @@
|
||||
#define PCI_PWR_CAP_BUDGET(x) ((x) & 1) /* Included in system budget */
|
||||
#define PCI_EXT_CAP_PWR_SIZEOF 16
|
||||
|
||||
/* Root Complex Event Collector Endpoint Association */
|
||||
#define PCI_RCEC_RCIEP_BITMAP 4 /* Associated Bitmap for RCiEPs */
|
||||
#define PCI_RCEC_BUSN 8 /* RCEC Associated Bus Numbers */
|
||||
#define PCI_RCEC_BUSN_REG_VER 0x02 /* Least version with BUSN present */
|
||||
#define PCI_RCEC_BUSN_NEXT(x) (((x) >> 8) & 0xff)
|
||||
#define PCI_RCEC_BUSN_LAST(x) (((x) >> 16) & 0xff)
|
||||
|
||||
/* Vendor-Specific (VSEC, PCI_EXT_CAP_ID_VNDR) */
|
||||
#define PCI_VNDR_HEADER 4 /* Vendor-Specific Header */
|
||||
#define PCI_VNDR_HEADER_ID(x) ((x) & 0xffff)
|
||||
|
Loading…
Reference in New Issue
Block a user