mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/
synced 2025-04-19 20:58:31 +09:00

-----BEGIN PGP SIGNATURE----- iQJIBAABCgAyFiEEgMe7l+5h9hnxdsnuWYigwDrT+vwFAmfmt1AUHGJoZWxnYWFz QGdvb2dsZS5jb20ACgkQWYigwDrT+vxqNw//cC1BlVe4UUVR5r9nfpoFeGAZeDJz 32naGvZKjwL0tR6dStO/BEZx4QrBp+smVfJfuxtQxfzLHLgMigM2jVhfa7XUmaun 7yZlJZu4Jmydc57sPf56CFOYOFP6zyPzSaE8u1Eb4IIqpvuoYpvDayDt6PSsLmFS PDzqmicT3nuNbbcfE4rYLyL6JsXooKCR1h+NNcDjy7Run9DvQbE6N2PPvXCu6O97 aC3+kYUydEpgn9DfjBDghe+GBQCkBPldwnWqXBxKDFmYj5bKFujNccS9/IDSEuuX oWntDRAXgLWg048sBC1AuJQajF3UaqffRGJkzUBaZWbU/jB9t5N/Z3GpYlXzizRx CAqnt/ciGUKVbaESKwoeeIqgK+wG1bnrmoEaJHXFGqjr6sjm2A2T5EzyBMJ1hFwE wUq6SDnkp5igG7rWtsBPo/lGa5h/pNlaXng11570ikD2ZfHVfRgwy2MpXYxChrkt X2q/lRYU7yyfNJQ8O5LQJ6bYztatjxT0TxXNFv+cxfVrdI7vnQMuaeBE352jn+Lo aZ8fTJDFbRXtrZolcoetZjBdHwPIS42wYQtvxo/ylUl64xKEzZEzN09XODjwn74v nuOzDtbM0TAjZWxi6bwRFPnemTEAQxPuv1i4VdPQ7yblvC0at2agNBsz6Fdq60GS s80YMJVUU0UcVoc= =gM1i -----END PGP SIGNATURE----- Merge tag 'pci-v6.15-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/pci/pci Pull pci updates from Bjorn Helgaas: "Enumeration: - Enable Configuration RRS SV, which makes device readiness visible, early instead of during child bus scanning (Bjorn Helgaas) - Log debug messages about reset methods being used (Bjorn Helgaas) - Avoid reset when it has been disabled via sysfs (Nishanth Aravamudan) - Add common pci-ep-bus.yaml schema for exporting several peripherals of a single PCI function via devicetree (Andrea della Porta) - Create DT nodes for PCI host bridges to enable loading device tree overlays to create platform devices for PCI devices that have several features that require multiple drivers (Herve Codina) Resource management: - Enlarge devres table[] to accommodate bridge windows, ROM, IOV BARs, etc., and validate BAR index in devres interfaces (Philipp Stanner) - Fix typo that repeatedly distributed resources to a bridge instead of iterating over subordinate bridges, which resulted in too little space to assign some BARs (Kai-Heng Feng) - Relax bridge window tail sizing for optional resources, e.g., IOV BARs, to avoid failures when removing and re-adding devices (Ilpo Järvinen) - Allow drivers to enable devices even if we haven't assigned optional IOV resources to them (Ilpo Järvinen) - Rework handling of optional resources (IOV BARs, ROMs) to reduce failures if we can't allocate them (Ilpo Järvinen) - Fix a NULL dereference in the SR-IOV VF creation error path (Shay Drory) - Fix s390 mmio_read/write syscalls, which didn't cause page faults in some cases, which broke vfio-pci lazy mapping on first access (Niklas Schnelle) - Add pdev->non_mappable_bars to replace CONFIG_VFIO_PCI_MMAP, which was disabled only for s390 (Niklas Schnelle) - Support mmap of PCI resources on s390 except for ISM devices (Niklas Schnelle) ASPM: - Delay pcie_link_state deallocation to avoid dangling pointers that cause invalid references during hot-unplug (Daniel Stodden) Power management: - Allow PCI bridges to go to D3Hot when suspending on all non-x86 systems (Manivannan Sadhasivam) Power control: - Create pwrctrl devices in pci_scan_device() to make it more symmetric with pci_pwrctrl_unregister() and make pwrctrl devices for PCI bridges possible (Manivannan Sadhasivam) - Unregister pwrctrl devices in pci_destroy_dev() so DOE, ASPM, etc. can still access devices after pci_stop_dev() (Manivannan Sadhasivam) - If there's a pwrctrl device for a PCI device, skip scanning it because the pwrctrl core will rescan the bus after the device is powered on (Manivannan Sadhasivam) - Add a pwrctrl driver for PCI slots based on voltage regulators described via devicetree (Manivannan Sadhasivam) Bandwidth control: - Add set_pcie_speed.sh to TEST_PROGS to fix issue when executing the set_pcie_cooling_state.sh test case (Yi Lai) - Avoid a NULL pointer dereference when we run out of bus numbers to assign for a bridge secondary bus (Lukas Wunner) Hotplug: - Drop superfluous pci_hotplug_slot_list, try_module_get() calls, and NULL pointer checks (Lukas Wunner) - Drop shpchp module init/exit logging, replace shpchp dbg() with ctrl_dbg(), and remove unused dbg(), err(), info(), warn() wrappers (Ilpo Järvinen) - Drop 'shpchp_debug' module parameter in favor of standard dynamic debugging (Ilpo Järvinen) - Drop unused cpcihp .get_power(), .set_power() function pointers (Guilherme Giacomo Simoes) - Disable hotplug interrupts in portdrv only when pciehp is not enabled to avoid issuing two hotplug commands too close together (Feng Tang) - Skip pciehp 'device replaced' check if the device has been removed to address a deadlock when resuming after a device was removed during system sleep (Lukas Wunner) - Don't enable pciehp hotplug interupt when resuming in poll mode (Ilpo Järvinen) Virtualization: - Fix bugs in 'pci=config_acs=' kernel command line parameter (Tushar Dave) DOE: - Expose supported DOE features via sysfs (Alistair Francis) - Allow DOE support to be enabled even if CXL isn't enabled (Alistair Francis) Endpoint framework: - Convert PCI device data so pci-epf-test works correctly on big-endian endpoint systems (Niklas Cassel) - Add BAR_RESIZABLE type to endpoint framework and add DWC core support for EPF drivers to set BAR_RESIZABLE type and size (Niklas Cassel) - Fix pci-epf-test double free that causes an oops if the host reboots and PERST# deassertion restarts endpoint BAR allocation (Christian Bruel) - Fix endpoint BAR testing so tests can skip disabled BARs instead of reporting them as failures (Niklas Cassel) - Widen endpoint test BAR size variable to accommodate BARs larger than INT_MAX (Niklas Cassel) - Remove unused tools 'pci' build target left over after moving tests to tools/testing/selftests/pci_endpoint (Jianfeng Liu) Altera PCIe controller driver: - Add DT binding and driver support for Agilex family (P-Tile, F-Tile, R-Tile) (Matthew Gerlach and D M, Sharath Kumar) AMD MDB PCIe controller driver: - Add DT binding and driver for AMD MDB (Multimedia DMA Bridge) (Thippeswamy Havalige) Broadcom STB PCIe controller driver: - Add BCM2712 MSI-X DT binding and interrupt controller drivers and add softdep on irq_bcm2712_mip driver to ensure that it is loaded first (Stanimir Varbanov) - Expand inbound window map to 64GB so it can accommodate BCM2712 (Stanimir Varbanov) - Add BCM2712 support and DT updates (Stanimir Varbanov) - Apply link speed restriction before bringing link up, not after (Jim Quinlan) - Update Max Link Speed in Link Capabilities via the internal writable register, not the read-only config register (Jim Quinlan) - Handle regulator_bulk_get() error to avoid panic when we call regulator_bulk_free() later (Jim Quinlan) - Disable regulators only when removing the bus immediately below a Root Port because we don't support regulators deeper in the hierarchy (Jim Quinlan) - Make const read-only arrays static (Colin Ian King) Cadence PCIe endpoint driver: - Correct MSG TLP generation so endpoints can generate INTx messages (Hans Zhang) Freescale i.MX6 PCIe controller driver: - Identify the second controller on i.MX8MQ based on devicetree 'linux,pci-domain' instead of DBI 'reg' address (Richard Zhu) - Remove imx_pcie_cpu_addr_fixup() since dwc core can now derive the ATU input address (using parent_bus_offset) from devicetree (Frank Li) Freescale Layerscape PCIe controller driver: - Drop deprecated 'num-ib-windows' and 'num-ob-windows' and unnecessary 'status' from example (Krzysztof Kozlowski) - Correct the syscon_regmap_lookup_by_phandle_args("fsl,pcie-scfg") arg_count to fix probe failure on LS1043A (Ioana Ciornei) HiSilicon STB PCIe controller driver: - Call phy_exit() to clean up if histb_pcie_probe() fails (Christophe JAILLET) Intel Gateway PCIe controller driver: - Remove intel_pcie_cpu_addr() since dwc core can now derive the ATU input address (using parent_bus_offset) from devicetree (Frank Li) Intel VMD host bridge driver: - Convert vmd_dev.cfg_lock from spinlock_t to raw_spinlock_t so pci_ops.read() will never sleep, even on PREEMPT_RT where spinlock_t becomes a sleepable lock, to avoid calling a sleeping function from invalid context (Ryo Takakura) MediaTek PCIe Gen3 controller driver: - Remove leftover mac_reset assert for Airoha EN7581 SoC (Lorenzo Bianconi) - Add EN7581 PBUS controller 'mediatek,pbus-csr' DT property and program host bridge memory aperture to this syscon node (Lorenzo Bianconi) Qualcomm PCIe controller driver: - Add qcom,pcie-ipq5332 binding (Varadarajan Narayanan) - Add qcom i.MX8QM and i.MX8QXP/DXP optional DMA interrupt (Alexander Stein) - Add optional dma-coherent DT property for Qualcomm SA8775P (Dmitry Baryshkov) - Make DT iommu property required for SA8775P and prohibited for SDX55 (Dmitry Baryshkov) - Add DT IOMMU and DMA-related properties for Qualcomm SM8450 (Dmitry Baryshkov) - Add endpoint DT properties for SAR2130P and enable endpoint mode in driver (Dmitry Baryshkov) - Describe endpoint BAR0 and BAR2 as 64-bit only and BAR1 and BAR3 as RESERVED (Manivannan Sadhasivam) Rockchip DesignWare PCIe controller driver: - Describe rk3568 and rk3588 BARs as Resizable, not Fixed (Niklas Cassel) Synopsys DesignWare PCIe controller driver: - Add debugfs-based Silicon Debug, Error Injection, Statistical Counter support for DWC (Shradha Todi) - Add debugfs property to expose LTSSM status of DWC PCIe link (Hans Zhang) - Add Rockchip support for DWC debugfs features (Niklas Cassel) - Add dw_pcie_parent_bus_offset() to look up the parent bus address of a specified 'reg' property and return the offset from the CPU physical address (Frank Li) - Use dw_pcie_parent_bus_offset() to derive CPU -> ATU addr offset via 'reg[config]' for host controllers and 'reg[addr_space]' for endpoint controllers (Frank Li) - Apply struct dw_pcie.parent_bus_offset in ATU users to remove use of .cpu_addr_fixup() when programming ATU (Frank Li) TI J721E PCIe driver: - Correct the 'link down' interrupt bit for J784S4 (Siddharth Vadapalli) TI Keystone PCIe controller driver: - Describe AM65x BARs 2 and 5 as Resizable (not Fixed) and reduce alignment requirement from 1MB to 64KB (Niklas Cassel) Xilinx Versal CPM PCIe controller driver: - Free IRQ domain in probe error path to avoid leaking it (Thippeswamy Havalige) - Add DT .compatible "xlnx,versal-cpm5nc-host" and driver support for Versal Net CPM5NC Root Port controller (Thippeswamy Havalige) - Add driver support for CPM5_HOST1 (Thippeswamy Havalige) Miscellaneous: - Convert fsl,mpc83xx-pcie binding to YAML (J. Neuschäfer) - Use for_each_available_child_of_node_scoped() to simplify apple, kirin, mediatek, mt7621, tegra drivers (Zhang Zekun)" * tag 'pci-v6.15-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/pci/pci: (197 commits) PCI: layerscape: Fix arg_count to syscon_regmap_lookup_by_phandle_args() PCI: j721e: Fix the value of .linkdown_irq_regfield for J784S4 misc: pci_endpoint_test: Add support for PCITEST_IRQ_TYPE_AUTO PCI: endpoint: pci-epf-test: Expose supported IRQ types in CAPS register PCI: dw-rockchip: Endpoint mode cannot raise INTx interrupts PCI: endpoint: Add intx_capable to epc_features struct dt-bindings: PCI: Add common schema for devices accessible through PCI BARs PCI: intel-gw: Remove intel_pcie_cpu_addr() PCI: imx6: Remove imx_pcie_cpu_addr_fixup() PCI: dwc: Use parent_bus_offset to remove need for .cpu_addr_fixup() PCI: dwc: ep: Ensure proper iteration over outbound map windows PCI: dwc: ep: Use devicetree 'reg[addr_space]' to derive CPU -> ATU addr offset PCI: dwc: ep: Consolidate devicetree handling in dw_pcie_ep_get_resources() PCI: dwc: ep: Call epc_create() early in dw_pcie_ep_init() PCI: dwc: Use devicetree 'reg[config]' to derive CPU -> ATU addr offset PCI: dwc: Add dw_pcie_parent_bus_offset() checking and debug PCI: dwc: Add dw_pcie_parent_bus_offset() PCI/bwctrl: Fix NULL pointer dereference on bus number exhaustion PCI: xilinx-cpm: Add cpm_csr register mapping for CPM5_HOST1 variant PCI: brcmstb: Make const read-only arrays static ...
791 lines
22 KiB
C
791 lines
22 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Synopsys DesignWare PCIe PMU driver
|
|
*
|
|
* Copyright (C) 2021-2023 Alibaba Inc.
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/cpuhotplug.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/device.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
#include <linux/pcie-dwc.h>
|
|
#include <linux/perf_event.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/types.h>
|
|
|
|
#define DWC_PCIE_EVENT_CNT_CTL 0x8
|
|
|
|
/*
|
|
* Event Counter Data Select includes two parts:
|
|
* - 27-24: Group number(4-bit: 0..0x7)
|
|
* - 23-16: Event number(8-bit: 0..0x13) within the Group
|
|
*
|
|
* Put them together as in TRM.
|
|
*/
|
|
#define DWC_PCIE_CNT_EVENT_SEL GENMASK(27, 16)
|
|
#define DWC_PCIE_CNT_LANE_SEL GENMASK(11, 8)
|
|
#define DWC_PCIE_CNT_STATUS BIT(7)
|
|
#define DWC_PCIE_CNT_ENABLE GENMASK(4, 2)
|
|
#define DWC_PCIE_PER_EVENT_OFF 0x1
|
|
#define DWC_PCIE_PER_EVENT_ON 0x3
|
|
#define DWC_PCIE_EVENT_CLEAR GENMASK(1, 0)
|
|
#define DWC_PCIE_EVENT_PER_CLEAR 0x1
|
|
|
|
#define DWC_PCIE_EVENT_CNT_DATA 0xC
|
|
|
|
#define DWC_PCIE_TIME_BASED_ANAL_CTL 0x10
|
|
#define DWC_PCIE_TIME_BASED_REPORT_SEL GENMASK(31, 24)
|
|
#define DWC_PCIE_TIME_BASED_DURATION_SEL GENMASK(15, 8)
|
|
#define DWC_PCIE_DURATION_MANUAL_CTL 0x0
|
|
#define DWC_PCIE_DURATION_1MS 0x1
|
|
#define DWC_PCIE_DURATION_10MS 0x2
|
|
#define DWC_PCIE_DURATION_100MS 0x3
|
|
#define DWC_PCIE_DURATION_1S 0x4
|
|
#define DWC_PCIE_DURATION_2S 0x5
|
|
#define DWC_PCIE_DURATION_4S 0x6
|
|
#define DWC_PCIE_DURATION_4US 0xFF
|
|
#define DWC_PCIE_TIME_BASED_TIMER_START BIT(0)
|
|
#define DWC_PCIE_TIME_BASED_CNT_ENABLE 0x1
|
|
|
|
#define DWC_PCIE_TIME_BASED_ANAL_DATA_REG_LOW 0x14
|
|
#define DWC_PCIE_TIME_BASED_ANAL_DATA_REG_HIGH 0x18
|
|
|
|
/* Event attributes */
|
|
#define DWC_PCIE_CONFIG_EVENTID GENMASK(15, 0)
|
|
#define DWC_PCIE_CONFIG_TYPE GENMASK(19, 16)
|
|
#define DWC_PCIE_CONFIG_LANE GENMASK(27, 20)
|
|
|
|
#define DWC_PCIE_EVENT_ID(event) FIELD_GET(DWC_PCIE_CONFIG_EVENTID, (event)->attr.config)
|
|
#define DWC_PCIE_EVENT_TYPE(event) FIELD_GET(DWC_PCIE_CONFIG_TYPE, (event)->attr.config)
|
|
#define DWC_PCIE_EVENT_LANE(event) FIELD_GET(DWC_PCIE_CONFIG_LANE, (event)->attr.config)
|
|
|
|
enum dwc_pcie_event_type {
|
|
DWC_PCIE_TIME_BASE_EVENT,
|
|
DWC_PCIE_LANE_EVENT,
|
|
DWC_PCIE_EVENT_TYPE_MAX,
|
|
};
|
|
|
|
#define DWC_PCIE_LANE_EVENT_MAX_PERIOD GENMASK_ULL(31, 0)
|
|
#define DWC_PCIE_MAX_PERIOD GENMASK_ULL(63, 0)
|
|
|
|
struct dwc_pcie_pmu {
|
|
struct pmu pmu;
|
|
struct pci_dev *pdev; /* Root Port device */
|
|
u16 ras_des_offset;
|
|
u32 nr_lanes;
|
|
|
|
struct hlist_node cpuhp_node;
|
|
struct perf_event *event[DWC_PCIE_EVENT_TYPE_MAX];
|
|
int on_cpu;
|
|
};
|
|
|
|
#define to_dwc_pcie_pmu(p) (container_of(p, struct dwc_pcie_pmu, pmu))
|
|
|
|
static int dwc_pcie_pmu_hp_state;
|
|
static struct list_head dwc_pcie_dev_info_head =
|
|
LIST_HEAD_INIT(dwc_pcie_dev_info_head);
|
|
static bool notify;
|
|
|
|
struct dwc_pcie_dev_info {
|
|
struct platform_device *plat_dev;
|
|
struct pci_dev *pdev;
|
|
struct list_head dev_node;
|
|
};
|
|
|
|
static ssize_t cpumask_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(dev_get_drvdata(dev));
|
|
|
|
return cpumap_print_to_pagebuf(true, buf, cpumask_of(pcie_pmu->on_cpu));
|
|
}
|
|
static DEVICE_ATTR_RO(cpumask);
|
|
|
|
static struct attribute *dwc_pcie_pmu_cpumask_attrs[] = {
|
|
&dev_attr_cpumask.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct attribute_group dwc_pcie_cpumask_attr_group = {
|
|
.attrs = dwc_pcie_pmu_cpumask_attrs,
|
|
};
|
|
|
|
struct dwc_pcie_format_attr {
|
|
struct device_attribute attr;
|
|
u64 field;
|
|
int config;
|
|
};
|
|
|
|
PMU_FORMAT_ATTR(eventid, "config:0-15");
|
|
PMU_FORMAT_ATTR(type, "config:16-19");
|
|
PMU_FORMAT_ATTR(lane, "config:20-27");
|
|
|
|
static struct attribute *dwc_pcie_format_attrs[] = {
|
|
&format_attr_type.attr,
|
|
&format_attr_eventid.attr,
|
|
&format_attr_lane.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group dwc_pcie_format_attrs_group = {
|
|
.name = "format",
|
|
.attrs = dwc_pcie_format_attrs,
|
|
};
|
|
|
|
struct dwc_pcie_event_attr {
|
|
struct device_attribute attr;
|
|
enum dwc_pcie_event_type type;
|
|
u16 eventid;
|
|
u8 lane;
|
|
};
|
|
|
|
static ssize_t dwc_pcie_event_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct dwc_pcie_event_attr *eattr;
|
|
|
|
eattr = container_of(attr, typeof(*eattr), attr);
|
|
|
|
if (eattr->type == DWC_PCIE_LANE_EVENT)
|
|
return sysfs_emit(buf, "eventid=0x%x,type=0x%x,lane=?\n",
|
|
eattr->eventid, eattr->type);
|
|
else if (eattr->type == DWC_PCIE_TIME_BASE_EVENT)
|
|
return sysfs_emit(buf, "eventid=0x%x,type=0x%x\n",
|
|
eattr->eventid, eattr->type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define DWC_PCIE_EVENT_ATTR(_name, _type, _eventid, _lane) \
|
|
(&((struct dwc_pcie_event_attr[]) {{ \
|
|
.attr = __ATTR(_name, 0444, dwc_pcie_event_show, NULL), \
|
|
.type = _type, \
|
|
.eventid = _eventid, \
|
|
.lane = _lane, \
|
|
}})[0].attr.attr)
|
|
|
|
#define DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(_name, _eventid) \
|
|
DWC_PCIE_EVENT_ATTR(_name, DWC_PCIE_TIME_BASE_EVENT, _eventid, 0)
|
|
#define DWC_PCIE_PMU_LANE_EVENT_ATTR(_name, _eventid) \
|
|
DWC_PCIE_EVENT_ATTR(_name, DWC_PCIE_LANE_EVENT, _eventid, 0)
|
|
|
|
static struct attribute *dwc_pcie_pmu_time_event_attrs[] = {
|
|
/* Group #0 */
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(one_cycle, 0x00),
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(TX_L0S, 0x01),
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(RX_L0S, 0x02),
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(L0, 0x03),
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(L1, 0x04),
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(L1_1, 0x05),
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(L1_2, 0x06),
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(CFG_RCVRY, 0x07),
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(L1_AUX, 0x08),
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(TX_RX_L0S, 0x09),
|
|
|
|
/* Group #1 */
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(tx_pcie_tlp_data_payload, 0x20),
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(rx_pcie_tlp_data_payload, 0x21),
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(tx_ccix_tlp_data_payload, 0x22),
|
|
DWC_PCIE_PMU_TIME_BASE_EVENT_ATTR(rx_ccix_tlp_data_payload, 0x23),
|
|
|
|
/*
|
|
* Leave it to the user to specify the lane ID to avoid generating
|
|
* a list of hundreds of events.
|
|
*/
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_ack_dllp, 0x600),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_update_fc_dllp, 0x601),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_ack_dllp, 0x602),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_update_fc_dllp, 0x603),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_nullified_tlp, 0x604),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_nullified_tlp, 0x605),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_duplicate_tlp, 0x606),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_memory_write, 0x700),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_memory_read, 0x701),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_configuration_write, 0x702),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_configuration_read, 0x703),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_io_write, 0x704),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_io_read, 0x705),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_completion_without_data, 0x706),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_completion_with_data, 0x707),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_message_tlp, 0x708),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_atomic, 0x709),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_tlp_with_prefix, 0x70A),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_memory_write, 0x70B),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_memory_read, 0x70C),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_io_write, 0x70F),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_io_read, 0x710),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_completion_without_data, 0x711),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_completion_with_data, 0x712),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_message_tlp, 0x713),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_atomic, 0x714),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_tlp_with_prefix, 0x715),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(tx_ccix_tlp, 0x716),
|
|
DWC_PCIE_PMU_LANE_EVENT_ATTR(rx_ccix_tlp, 0x717),
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group dwc_pcie_event_attrs_group = {
|
|
.name = "events",
|
|
.attrs = dwc_pcie_pmu_time_event_attrs,
|
|
};
|
|
|
|
static const struct attribute_group *dwc_pcie_attr_groups[] = {
|
|
&dwc_pcie_event_attrs_group,
|
|
&dwc_pcie_format_attrs_group,
|
|
&dwc_pcie_cpumask_attr_group,
|
|
NULL
|
|
};
|
|
|
|
static void dwc_pcie_pmu_lane_event_enable(struct dwc_pcie_pmu *pcie_pmu,
|
|
bool enable)
|
|
{
|
|
struct pci_dev *pdev = pcie_pmu->pdev;
|
|
u16 ras_des_offset = pcie_pmu->ras_des_offset;
|
|
|
|
if (enable)
|
|
pci_clear_and_set_config_dword(pdev,
|
|
ras_des_offset + DWC_PCIE_EVENT_CNT_CTL,
|
|
DWC_PCIE_CNT_ENABLE, DWC_PCIE_PER_EVENT_ON);
|
|
else
|
|
pci_clear_and_set_config_dword(pdev,
|
|
ras_des_offset + DWC_PCIE_EVENT_CNT_CTL,
|
|
DWC_PCIE_CNT_ENABLE, DWC_PCIE_PER_EVENT_OFF);
|
|
}
|
|
|
|
static void dwc_pcie_pmu_time_based_event_enable(struct dwc_pcie_pmu *pcie_pmu,
|
|
bool enable)
|
|
{
|
|
struct pci_dev *pdev = pcie_pmu->pdev;
|
|
u16 ras_des_offset = pcie_pmu->ras_des_offset;
|
|
|
|
pci_clear_and_set_config_dword(pdev,
|
|
ras_des_offset + DWC_PCIE_TIME_BASED_ANAL_CTL,
|
|
DWC_PCIE_TIME_BASED_TIMER_START, enable);
|
|
}
|
|
|
|
static u64 dwc_pcie_pmu_read_lane_event_counter(struct perf_event *event)
|
|
{
|
|
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
|
|
struct pci_dev *pdev = pcie_pmu->pdev;
|
|
u16 ras_des_offset = pcie_pmu->ras_des_offset;
|
|
u32 val;
|
|
|
|
pci_read_config_dword(pdev, ras_des_offset + DWC_PCIE_EVENT_CNT_DATA, &val);
|
|
|
|
return val;
|
|
}
|
|
|
|
static u64 dwc_pcie_pmu_read_time_based_counter(struct perf_event *event)
|
|
{
|
|
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
|
|
struct pci_dev *pdev = pcie_pmu->pdev;
|
|
int event_id = DWC_PCIE_EVENT_ID(event);
|
|
u16 ras_des_offset = pcie_pmu->ras_des_offset;
|
|
u32 lo, hi, ss;
|
|
u64 val;
|
|
|
|
/*
|
|
* The 64-bit value of the data counter is spread across two
|
|
* registers that are not synchronized. In order to read them
|
|
* atomically, ensure that the high 32 bits match before and after
|
|
* reading the low 32 bits.
|
|
*/
|
|
pci_read_config_dword(pdev,
|
|
ras_des_offset + DWC_PCIE_TIME_BASED_ANAL_DATA_REG_HIGH, &hi);
|
|
do {
|
|
/* snapshot the high 32 bits */
|
|
ss = hi;
|
|
|
|
pci_read_config_dword(
|
|
pdev, ras_des_offset + DWC_PCIE_TIME_BASED_ANAL_DATA_REG_LOW,
|
|
&lo);
|
|
pci_read_config_dword(
|
|
pdev, ras_des_offset + DWC_PCIE_TIME_BASED_ANAL_DATA_REG_HIGH,
|
|
&hi);
|
|
} while (hi != ss);
|
|
|
|
val = ((u64)hi << 32) | lo;
|
|
/*
|
|
* The Group#1 event measures the amount of data processed in 16-byte
|
|
* units. Simplify the end-user interface by multiplying the counter
|
|
* at the point of read.
|
|
*/
|
|
if (event_id >= 0x20 && event_id <= 0x23)
|
|
val *= 16;
|
|
|
|
return val;
|
|
}
|
|
|
|
static void dwc_pcie_pmu_event_update(struct perf_event *event)
|
|
{
|
|
struct hw_perf_event *hwc = &event->hw;
|
|
enum dwc_pcie_event_type type = DWC_PCIE_EVENT_TYPE(event);
|
|
u64 delta, prev, now = 0;
|
|
|
|
do {
|
|
prev = local64_read(&hwc->prev_count);
|
|
|
|
if (type == DWC_PCIE_LANE_EVENT)
|
|
now = dwc_pcie_pmu_read_lane_event_counter(event);
|
|
else if (type == DWC_PCIE_TIME_BASE_EVENT)
|
|
now = dwc_pcie_pmu_read_time_based_counter(event);
|
|
|
|
} while (local64_cmpxchg(&hwc->prev_count, prev, now) != prev);
|
|
|
|
delta = (now - prev) & DWC_PCIE_MAX_PERIOD;
|
|
/* 32-bit counter for Lane Event Counting */
|
|
if (type == DWC_PCIE_LANE_EVENT)
|
|
delta &= DWC_PCIE_LANE_EVENT_MAX_PERIOD;
|
|
|
|
local64_add(delta, &event->count);
|
|
}
|
|
|
|
static int dwc_pcie_pmu_event_init(struct perf_event *event)
|
|
{
|
|
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
|
|
enum dwc_pcie_event_type type = DWC_PCIE_EVENT_TYPE(event);
|
|
struct perf_event *sibling;
|
|
u32 lane;
|
|
|
|
if (event->attr.type != event->pmu->type)
|
|
return -ENOENT;
|
|
|
|
/* We don't support sampling */
|
|
if (is_sampling_event(event))
|
|
return -EINVAL;
|
|
|
|
/* We cannot support task bound events */
|
|
if (event->cpu < 0 || event->attach_state & PERF_ATTACH_TASK)
|
|
return -EINVAL;
|
|
|
|
if (event->group_leader != event &&
|
|
!is_software_event(event->group_leader))
|
|
return -EINVAL;
|
|
|
|
for_each_sibling_event(sibling, event->group_leader) {
|
|
if (sibling->pmu != event->pmu && !is_software_event(sibling))
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (type < 0 || type >= DWC_PCIE_EVENT_TYPE_MAX)
|
|
return -EINVAL;
|
|
|
|
if (type == DWC_PCIE_LANE_EVENT) {
|
|
lane = DWC_PCIE_EVENT_LANE(event);
|
|
if (lane < 0 || lane >= pcie_pmu->nr_lanes)
|
|
return -EINVAL;
|
|
}
|
|
|
|
event->cpu = pcie_pmu->on_cpu;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dwc_pcie_pmu_event_start(struct perf_event *event, int flags)
|
|
{
|
|
struct hw_perf_event *hwc = &event->hw;
|
|
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
|
|
enum dwc_pcie_event_type type = DWC_PCIE_EVENT_TYPE(event);
|
|
|
|
hwc->state = 0;
|
|
local64_set(&hwc->prev_count, 0);
|
|
|
|
if (type == DWC_PCIE_LANE_EVENT)
|
|
dwc_pcie_pmu_lane_event_enable(pcie_pmu, true);
|
|
else if (type == DWC_PCIE_TIME_BASE_EVENT)
|
|
dwc_pcie_pmu_time_based_event_enable(pcie_pmu, true);
|
|
}
|
|
|
|
static void dwc_pcie_pmu_event_stop(struct perf_event *event, int flags)
|
|
{
|
|
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
|
|
enum dwc_pcie_event_type type = DWC_PCIE_EVENT_TYPE(event);
|
|
struct hw_perf_event *hwc = &event->hw;
|
|
|
|
if (event->hw.state & PERF_HES_STOPPED)
|
|
return;
|
|
|
|
if (type == DWC_PCIE_LANE_EVENT)
|
|
dwc_pcie_pmu_lane_event_enable(pcie_pmu, false);
|
|
else if (type == DWC_PCIE_TIME_BASE_EVENT)
|
|
dwc_pcie_pmu_time_based_event_enable(pcie_pmu, false);
|
|
|
|
dwc_pcie_pmu_event_update(event);
|
|
hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
|
|
}
|
|
|
|
static int dwc_pcie_pmu_event_add(struct perf_event *event, int flags)
|
|
{
|
|
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
|
|
struct pci_dev *pdev = pcie_pmu->pdev;
|
|
struct hw_perf_event *hwc = &event->hw;
|
|
enum dwc_pcie_event_type type = DWC_PCIE_EVENT_TYPE(event);
|
|
int event_id = DWC_PCIE_EVENT_ID(event);
|
|
int lane = DWC_PCIE_EVENT_LANE(event);
|
|
u16 ras_des_offset = pcie_pmu->ras_des_offset;
|
|
u32 ctrl;
|
|
|
|
/* one counter for each type and it is in use */
|
|
if (pcie_pmu->event[type])
|
|
return -ENOSPC;
|
|
|
|
pcie_pmu->event[type] = event;
|
|
hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
|
|
|
|
if (type == DWC_PCIE_LANE_EVENT) {
|
|
/* EVENT_COUNTER_DATA_REG needs clear manually */
|
|
ctrl = FIELD_PREP(DWC_PCIE_CNT_EVENT_SEL, event_id) |
|
|
FIELD_PREP(DWC_PCIE_CNT_LANE_SEL, lane) |
|
|
FIELD_PREP(DWC_PCIE_CNT_ENABLE, DWC_PCIE_PER_EVENT_OFF) |
|
|
FIELD_PREP(DWC_PCIE_EVENT_CLEAR, DWC_PCIE_EVENT_PER_CLEAR);
|
|
pci_write_config_dword(pdev, ras_des_offset + DWC_PCIE_EVENT_CNT_CTL,
|
|
ctrl);
|
|
} else if (type == DWC_PCIE_TIME_BASE_EVENT) {
|
|
/*
|
|
* TIME_BASED_ANAL_DATA_REG is a 64 bit register, we can safely
|
|
* use it with any manually controlled duration. And it is
|
|
* cleared when next measurement starts.
|
|
*/
|
|
ctrl = FIELD_PREP(DWC_PCIE_TIME_BASED_REPORT_SEL, event_id) |
|
|
FIELD_PREP(DWC_PCIE_TIME_BASED_DURATION_SEL,
|
|
DWC_PCIE_DURATION_MANUAL_CTL) |
|
|
DWC_PCIE_TIME_BASED_CNT_ENABLE;
|
|
pci_write_config_dword(
|
|
pdev, ras_des_offset + DWC_PCIE_TIME_BASED_ANAL_CTL, ctrl);
|
|
}
|
|
|
|
if (flags & PERF_EF_START)
|
|
dwc_pcie_pmu_event_start(event, PERF_EF_RELOAD);
|
|
|
|
perf_event_update_userpage(event);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dwc_pcie_pmu_event_del(struct perf_event *event, int flags)
|
|
{
|
|
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
|
|
enum dwc_pcie_event_type type = DWC_PCIE_EVENT_TYPE(event);
|
|
|
|
dwc_pcie_pmu_event_stop(event, flags | PERF_EF_UPDATE);
|
|
perf_event_update_userpage(event);
|
|
pcie_pmu->event[type] = NULL;
|
|
}
|
|
|
|
static void dwc_pcie_pmu_remove_cpuhp_instance(void *hotplug_node)
|
|
{
|
|
cpuhp_state_remove_instance_nocalls(dwc_pcie_pmu_hp_state, hotplug_node);
|
|
}
|
|
|
|
/*
|
|
* Find the binded DES capability device info of a PCI device.
|
|
* @pdev: The PCI device.
|
|
*/
|
|
static struct dwc_pcie_dev_info *dwc_pcie_find_dev_info(struct pci_dev *pdev)
|
|
{
|
|
struct dwc_pcie_dev_info *dev_info;
|
|
|
|
list_for_each_entry(dev_info, &dwc_pcie_dev_info_head, dev_node)
|
|
if (dev_info->pdev == pdev)
|
|
return dev_info;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void dwc_pcie_unregister_pmu(void *data)
|
|
{
|
|
struct dwc_pcie_pmu *pcie_pmu = data;
|
|
|
|
perf_pmu_unregister(&pcie_pmu->pmu);
|
|
}
|
|
|
|
static u16 dwc_pcie_des_cap(struct pci_dev *pdev)
|
|
{
|
|
const struct dwc_pcie_vsec_id *vid;
|
|
u16 vsec;
|
|
u32 val;
|
|
|
|
if (!pci_is_pcie(pdev) || !(pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT))
|
|
return 0;
|
|
|
|
for (vid = dwc_pcie_rasdes_vsec_ids; vid->vendor_id; vid++) {
|
|
vsec = pci_find_vsec_capability(pdev, vid->vendor_id,
|
|
vid->vsec_id);
|
|
if (vsec) {
|
|
pci_read_config_dword(pdev, vsec + PCI_VNDR_HEADER,
|
|
&val);
|
|
if (PCI_VNDR_HEADER_REV(val) == vid->vsec_rev) {
|
|
pci_dbg(pdev, "Detected PCIe Vendor-Specific Extended Capability RAS DES\n");
|
|
return vsec;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void dwc_pcie_unregister_dev(struct dwc_pcie_dev_info *dev_info)
|
|
{
|
|
platform_device_unregister(dev_info->plat_dev);
|
|
list_del(&dev_info->dev_node);
|
|
kfree(dev_info);
|
|
}
|
|
|
|
static int dwc_pcie_register_dev(struct pci_dev *pdev)
|
|
{
|
|
struct platform_device *plat_dev;
|
|
struct dwc_pcie_dev_info *dev_info;
|
|
u32 sbdf;
|
|
|
|
sbdf = (pci_domain_nr(pdev->bus) << 16) | PCI_DEVID(pdev->bus->number, pdev->devfn);
|
|
plat_dev = platform_device_register_simple("dwc_pcie_pmu", sbdf, NULL, 0);
|
|
if (IS_ERR(plat_dev))
|
|
return PTR_ERR(plat_dev);
|
|
|
|
dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL);
|
|
if (!dev_info) {
|
|
platform_device_unregister(plat_dev);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Cache platform device to handle pci device hotplug */
|
|
dev_info->plat_dev = plat_dev;
|
|
dev_info->pdev = pdev;
|
|
list_add(&dev_info->dev_node, &dwc_pcie_dev_info_head);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwc_pcie_pmu_notifier(struct notifier_block *nb,
|
|
unsigned long action, void *data)
|
|
{
|
|
struct device *dev = data;
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
struct dwc_pcie_dev_info *dev_info;
|
|
|
|
switch (action) {
|
|
case BUS_NOTIFY_ADD_DEVICE:
|
|
if (!dwc_pcie_des_cap(pdev))
|
|
return NOTIFY_DONE;
|
|
if (dwc_pcie_register_dev(pdev))
|
|
return NOTIFY_BAD;
|
|
break;
|
|
case BUS_NOTIFY_DEL_DEVICE:
|
|
dev_info = dwc_pcie_find_dev_info(pdev);
|
|
if (!dev_info)
|
|
return NOTIFY_DONE;
|
|
dwc_pcie_unregister_dev(dev_info);
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block dwc_pcie_pmu_nb = {
|
|
.notifier_call = dwc_pcie_pmu_notifier,
|
|
};
|
|
|
|
static int dwc_pcie_pmu_probe(struct platform_device *plat_dev)
|
|
{
|
|
struct pci_dev *pdev;
|
|
struct dwc_pcie_pmu *pcie_pmu;
|
|
char *name;
|
|
u32 sbdf;
|
|
u16 vsec;
|
|
int ret;
|
|
|
|
sbdf = plat_dev->id;
|
|
pdev = pci_get_domain_bus_and_slot(sbdf >> 16, PCI_BUS_NUM(sbdf & 0xffff),
|
|
sbdf & 0xff);
|
|
if (!pdev) {
|
|
pr_err("No pdev found for the sbdf 0x%x\n", sbdf);
|
|
return -ENODEV;
|
|
}
|
|
|
|
vsec = dwc_pcie_des_cap(pdev);
|
|
if (!vsec)
|
|
return -ENODEV;
|
|
|
|
pci_dev_put(pdev);
|
|
name = devm_kasprintf(&plat_dev->dev, GFP_KERNEL, "dwc_rootport_%x", sbdf);
|
|
if (!name)
|
|
return -ENOMEM;
|
|
|
|
pcie_pmu = devm_kzalloc(&plat_dev->dev, sizeof(*pcie_pmu), GFP_KERNEL);
|
|
if (!pcie_pmu)
|
|
return -ENOMEM;
|
|
|
|
pcie_pmu->pdev = pdev;
|
|
pcie_pmu->ras_des_offset = vsec;
|
|
pcie_pmu->nr_lanes = pcie_get_width_cap(pdev);
|
|
pcie_pmu->on_cpu = -1;
|
|
pcie_pmu->pmu = (struct pmu){
|
|
.name = name,
|
|
.parent = &plat_dev->dev,
|
|
.module = THIS_MODULE,
|
|
.attr_groups = dwc_pcie_attr_groups,
|
|
.capabilities = PERF_PMU_CAP_NO_EXCLUDE,
|
|
.task_ctx_nr = perf_invalid_context,
|
|
.event_init = dwc_pcie_pmu_event_init,
|
|
.add = dwc_pcie_pmu_event_add,
|
|
.del = dwc_pcie_pmu_event_del,
|
|
.start = dwc_pcie_pmu_event_start,
|
|
.stop = dwc_pcie_pmu_event_stop,
|
|
.read = dwc_pcie_pmu_event_update,
|
|
};
|
|
|
|
/* Add this instance to the list used by the offline callback */
|
|
ret = cpuhp_state_add_instance(dwc_pcie_pmu_hp_state,
|
|
&pcie_pmu->cpuhp_node);
|
|
if (ret) {
|
|
pci_err(pdev, "Error %d registering hotplug @%x\n", ret, sbdf);
|
|
return ret;
|
|
}
|
|
|
|
/* Unwind when platform driver removes */
|
|
ret = devm_add_action_or_reset(&plat_dev->dev,
|
|
dwc_pcie_pmu_remove_cpuhp_instance,
|
|
&pcie_pmu->cpuhp_node);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = perf_pmu_register(&pcie_pmu->pmu, name, -1);
|
|
if (ret) {
|
|
pci_err(pdev, "Error %d registering PMU @%x\n", ret, sbdf);
|
|
return ret;
|
|
}
|
|
ret = devm_add_action_or_reset(&plat_dev->dev, dwc_pcie_unregister_pmu,
|
|
pcie_pmu);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwc_pcie_pmu_online_cpu(unsigned int cpu, struct hlist_node *cpuhp_node)
|
|
{
|
|
struct dwc_pcie_pmu *pcie_pmu;
|
|
|
|
pcie_pmu = hlist_entry_safe(cpuhp_node, struct dwc_pcie_pmu, cpuhp_node);
|
|
if (pcie_pmu->on_cpu == -1)
|
|
pcie_pmu->on_cpu = cpumask_local_spread(
|
|
0, dev_to_node(&pcie_pmu->pdev->dev));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwc_pcie_pmu_offline_cpu(unsigned int cpu, struct hlist_node *cpuhp_node)
|
|
{
|
|
struct dwc_pcie_pmu *pcie_pmu;
|
|
struct pci_dev *pdev;
|
|
unsigned int target;
|
|
int node;
|
|
|
|
pcie_pmu = hlist_entry_safe(cpuhp_node, struct dwc_pcie_pmu, cpuhp_node);
|
|
/* Nothing to do if this CPU doesn't own the PMU */
|
|
if (cpu != pcie_pmu->on_cpu)
|
|
return 0;
|
|
|
|
pcie_pmu->on_cpu = -1;
|
|
pdev = pcie_pmu->pdev;
|
|
node = dev_to_node(&pdev->dev);
|
|
|
|
target = cpumask_any_and_but(cpumask_of_node(node), cpu_online_mask, cpu);
|
|
if (target >= nr_cpu_ids)
|
|
target = cpumask_any_but(cpu_online_mask, cpu);
|
|
|
|
if (target >= nr_cpu_ids) {
|
|
pci_err(pdev, "There is no CPU to set\n");
|
|
return 0;
|
|
}
|
|
|
|
/* This PMU does NOT support interrupt, just migrate context. */
|
|
perf_pmu_migrate_context(&pcie_pmu->pmu, cpu, target);
|
|
pcie_pmu->on_cpu = target;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver dwc_pcie_pmu_driver = {
|
|
.probe = dwc_pcie_pmu_probe,
|
|
.driver = {.name = "dwc_pcie_pmu",},
|
|
};
|
|
|
|
static void dwc_pcie_cleanup_devices(void)
|
|
{
|
|
struct dwc_pcie_dev_info *dev_info, *tmp;
|
|
|
|
list_for_each_entry_safe(dev_info, tmp, &dwc_pcie_dev_info_head, dev_node) {
|
|
dwc_pcie_unregister_dev(dev_info);
|
|
}
|
|
}
|
|
|
|
static int __init dwc_pcie_pmu_init(void)
|
|
{
|
|
struct pci_dev *pdev = NULL;
|
|
int ret;
|
|
|
|
for_each_pci_dev(pdev) {
|
|
if (!dwc_pcie_des_cap(pdev))
|
|
continue;
|
|
|
|
ret = dwc_pcie_register_dev(pdev);
|
|
if (ret) {
|
|
pci_dev_put(pdev);
|
|
goto err_cleanup;
|
|
}
|
|
}
|
|
|
|
ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN,
|
|
"perf/dwc_pcie_pmu:online",
|
|
dwc_pcie_pmu_online_cpu,
|
|
dwc_pcie_pmu_offline_cpu);
|
|
if (ret < 0)
|
|
goto err_cleanup;
|
|
|
|
dwc_pcie_pmu_hp_state = ret;
|
|
|
|
ret = platform_driver_register(&dwc_pcie_pmu_driver);
|
|
if (ret)
|
|
goto err_remove_cpuhp;
|
|
|
|
ret = bus_register_notifier(&pci_bus_type, &dwc_pcie_pmu_nb);
|
|
if (ret)
|
|
goto err_unregister_driver;
|
|
notify = true;
|
|
|
|
return 0;
|
|
|
|
err_unregister_driver:
|
|
platform_driver_unregister(&dwc_pcie_pmu_driver);
|
|
err_remove_cpuhp:
|
|
cpuhp_remove_multi_state(dwc_pcie_pmu_hp_state);
|
|
err_cleanup:
|
|
dwc_pcie_cleanup_devices();
|
|
return ret;
|
|
}
|
|
|
|
static void __exit dwc_pcie_pmu_exit(void)
|
|
{
|
|
if (notify)
|
|
bus_unregister_notifier(&pci_bus_type, &dwc_pcie_pmu_nb);
|
|
dwc_pcie_cleanup_devices();
|
|
platform_driver_unregister(&dwc_pcie_pmu_driver);
|
|
cpuhp_remove_multi_state(dwc_pcie_pmu_hp_state);
|
|
}
|
|
|
|
module_init(dwc_pcie_pmu_init);
|
|
module_exit(dwc_pcie_pmu_exit);
|
|
|
|
MODULE_DESCRIPTION("PMU driver for DesignWare Cores PCI Express Controller");
|
|
MODULE_AUTHOR("Shuai Xue <xueshuai@linux.alibaba.com>");
|
|
MODULE_LICENSE("GPL v2");
|