mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-18 22:14:16 +00:00
powerpc fixes for 6.17 #2
- Fixes for several issues in the powernv PCI hotplug path - Fix htmldoc generation for htm.rst in toctree - Add jit support for load_acquire and store_release in ppc64 bpf jit Thanks to: Bjorn Helgaas, Hari Bathini, Puranjay Mohan, Saket Kumar Bhaskar, Shawn Anastasio, Timothy Pearson, Vishal Parmar -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEqX2DNAOgU8sBX3pRpnEsdPSHZJQFAmiQDOcACgkQpnEsdPSH ZJRqTBAAkpaZRrbzX6P1fjoTb89esIfY7YSymmXrgMwoF1fgTMzPSjg2Lzwzcjfb Ednlo8Hy+Ei2SDvctNaqtOcZuidn81AJN41aFIu44S/Mj1cde/cuBMKUTqx+ZBO0 gI4Bd4+V7yEv484PF7ZJga9K1VM85THCLgVWJ00VNHVHyvxgAuFiXdWmh6/qMUyi thvvLR+ANuAQz3S4VwbBg3AifDl6LXx2s5VB30xYxnPKzFNKZmnGXKwuOJH7rQe2 J8v99n0tcXW1tRGE4pVykzXg4EXL5zgWT9fJ5EZxbeXaW9sqMxi4VjO4jSsrSZ+K q2v362Dyjgygel9aC2rzN8Q+P5horX2QBR7knJJGa0VtztUiPWKR8za7vGLbzlcm rUvnTuoY1dwFB72Sy3amALalvWscssL/1sHazvRv65RcciW7/PZNVEiZ0xh1RKqb J8nWlb+iNBf2z12qLmS5DvUaveaZG1eyndLeD/knsEC49DEOoEy3t7QM1F7KscRR mYPsEpfjF/D0r+vzb6zl2ykwhJf/t7BNu7MdXH7xbIpj5iwtrhSUfvmx6g2MThzA Vee2QvQACscdop3W/6xgATH4xoq96v1XxMmCLnZ/HVl2PorxO27ad4EMNO4sBG8c 5agWHT7EnoUgwNF30DRtIHd7jNK/jt8++3kZx6CG9hdSboAM/pM= =tTms -----END PGP SIGNATURE----- Merge tag 'powerpc-6.17-2' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux Pull powerpc fixes from Madhavan Srinivasan: - Fixes for several issues in the powernv PCI hotplug path - Fix htmldoc generation for htm.rst in toctree - Add jit support for load_acquire and store_release in ppc64 bpf jit Thanks to Bjorn Helgaas, Hari Bathini, Puranjay Mohan, Saket Kumar Bhaskar, Shawn Anastasio, Timothy Pearson, and Vishal Parmar * tag 'powerpc-6.17-2' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux: powerpc64/bpf: Add jit support for load_acquire and store_release docs: powerpc: add htm.rst to toctree PCI: pnv_php: Enable third attention indicator state PCI: pnv_php: Fix surprise plug detection and recovery powerpc/eeh: Make EEH driver device hotplug safe powerpc/eeh: Export eeh_unfreeze_pe() PCI: pnv_php: Work around switches with broken presence detection PCI: pnv_php: Clean up allocated IRQs on unplug
This commit is contained in:
commit
806381e1a2
8 changed files with 353 additions and 45 deletions
|
@ -425,6 +425,7 @@
|
|||
#define PPC_RAW_SC() (0x44000002)
|
||||
#define PPC_RAW_SYNC() (0x7c0004ac)
|
||||
#define PPC_RAW_ISYNC() (0x4c00012c)
|
||||
#define PPC_RAW_LWSYNC() (0x7c2004ac)
|
||||
|
||||
/*
|
||||
* Define what the VSX XX1 form instructions will look like, then add
|
||||
|
|
|
@ -1139,6 +1139,7 @@ int eeh_unfreeze_pe(struct eeh_pe *pe)
|
|||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(eeh_unfreeze_pe);
|
||||
|
||||
|
||||
static struct pci_device_id eeh_reset_ids[] = {
|
||||
|
|
|
@ -257,13 +257,12 @@ static void eeh_pe_report_edev(struct eeh_dev *edev, eeh_report_fn fn,
|
|||
struct pci_driver *driver;
|
||||
enum pci_ers_result new_result;
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
pdev = edev->pdev;
|
||||
if (pdev)
|
||||
get_device(&pdev->dev);
|
||||
pci_unlock_rescan_remove();
|
||||
if (!pdev) {
|
||||
eeh_edev_info(edev, "no device");
|
||||
*result = PCI_ERS_RESULT_DISCONNECT;
|
||||
return;
|
||||
}
|
||||
device_lock(&pdev->dev);
|
||||
|
@ -304,8 +303,9 @@ static void eeh_pe_report(const char *name, struct eeh_pe *root,
|
|||
struct eeh_dev *edev, *tmp;
|
||||
|
||||
pr_info("EEH: Beginning: '%s'\n", name);
|
||||
eeh_for_each_pe(root, pe) eeh_pe_for_each_dev(pe, edev, tmp)
|
||||
eeh_pe_report_edev(edev, fn, result);
|
||||
eeh_for_each_pe(root, pe)
|
||||
eeh_pe_for_each_dev(pe, edev, tmp)
|
||||
eeh_pe_report_edev(edev, fn, result);
|
||||
if (result)
|
||||
pr_info("EEH: Finished:'%s' with aggregate recovery state:'%s'\n",
|
||||
name, pci_ers_result_name(*result));
|
||||
|
@ -383,6 +383,8 @@ static void eeh_dev_restore_state(struct eeh_dev *edev, void *userdata)
|
|||
if (!edev)
|
||||
return;
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
|
||||
/*
|
||||
* The content in the config space isn't saved because
|
||||
* the blocked config space on some adapters. We have
|
||||
|
@ -393,14 +395,19 @@ static void eeh_dev_restore_state(struct eeh_dev *edev, void *userdata)
|
|||
if (list_is_last(&edev->entry, &edev->pe->edevs))
|
||||
eeh_pe_restore_bars(edev->pe);
|
||||
|
||||
pci_unlock_rescan_remove();
|
||||
return;
|
||||
}
|
||||
|
||||
pdev = eeh_dev_to_pci_dev(edev);
|
||||
if (!pdev)
|
||||
if (!pdev) {
|
||||
pci_unlock_rescan_remove();
|
||||
return;
|
||||
}
|
||||
|
||||
pci_restore_state(pdev);
|
||||
|
||||
pci_unlock_rescan_remove();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -647,9 +654,7 @@ static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus,
|
|||
if (any_passed || driver_eeh_aware || (pe->type & EEH_PE_VF)) {
|
||||
eeh_pe_dev_traverse(pe, eeh_rmv_device, rmv_data);
|
||||
} else {
|
||||
pci_lock_rescan_remove();
|
||||
pci_hp_remove_devices(bus);
|
||||
pci_unlock_rescan_remove();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -665,8 +670,6 @@ static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus,
|
|||
if (rc)
|
||||
return rc;
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
|
||||
/* Restore PE */
|
||||
eeh_ops->configure_bridge(pe);
|
||||
eeh_pe_restore_bars(pe);
|
||||
|
@ -674,7 +677,6 @@ static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus,
|
|||
/* Clear frozen state */
|
||||
rc = eeh_clear_pe_frozen_state(pe, false);
|
||||
if (rc) {
|
||||
pci_unlock_rescan_remove();
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
@ -709,7 +711,6 @@ static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus,
|
|||
pe->tstamp = tstamp;
|
||||
pe->freeze_count = cnt;
|
||||
|
||||
pci_unlock_rescan_remove();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -843,10 +844,13 @@ void eeh_handle_normal_event(struct eeh_pe *pe)
|
|||
{LIST_HEAD_INIT(rmv_data.removed_vf_list), 0};
|
||||
int devices = 0;
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
|
||||
bus = eeh_pe_bus_get(pe);
|
||||
if (!bus) {
|
||||
pr_err("%s: Cannot find PCI bus for PHB#%x-PE#%x\n",
|
||||
__func__, pe->phb->global_number, pe->addr);
|
||||
pci_unlock_rescan_remove();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1094,10 +1098,15 @@ recover_failed:
|
|||
eeh_pe_state_clear(pe, EEH_PE_PRI_BUS, true);
|
||||
eeh_pe_dev_mode_mark(pe, EEH_DEV_REMOVED);
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
pci_hp_remove_devices(bus);
|
||||
pci_unlock_rescan_remove();
|
||||
bus = eeh_pe_bus_get(pe);
|
||||
if (bus)
|
||||
pci_hp_remove_devices(bus);
|
||||
else
|
||||
pr_err("%s: PCI bus for PHB#%x-PE#%x disappeared\n",
|
||||
__func__, pe->phb->global_number, pe->addr);
|
||||
|
||||
/* The passed PE should no longer be used */
|
||||
pci_unlock_rescan_remove();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1114,6 +1123,8 @@ out:
|
|||
eeh_clear_slot_attention(edev->pdev);
|
||||
|
||||
eeh_pe_state_clear(pe, EEH_PE_RECOVERING, true);
|
||||
|
||||
pci_unlock_rescan_remove();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1132,6 +1143,7 @@ void eeh_handle_special_event(void)
|
|||
unsigned long flags;
|
||||
int rc;
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
|
||||
do {
|
||||
rc = eeh_ops->next_error(&pe);
|
||||
|
@ -1171,10 +1183,12 @@ void eeh_handle_special_event(void)
|
|||
|
||||
break;
|
||||
case EEH_NEXT_ERR_NONE:
|
||||
pci_unlock_rescan_remove();
|
||||
return;
|
||||
default:
|
||||
pr_warn("%s: Invalid value %d from next_error()\n",
|
||||
__func__, rc);
|
||||
pci_unlock_rescan_remove();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1186,7 +1200,9 @@ void eeh_handle_special_event(void)
|
|||
if (rc == EEH_NEXT_ERR_FROZEN_PE ||
|
||||
rc == EEH_NEXT_ERR_FENCED_PHB) {
|
||||
eeh_pe_state_mark(pe, EEH_PE_RECOVERING);
|
||||
pci_unlock_rescan_remove();
|
||||
eeh_handle_normal_event(pe);
|
||||
pci_lock_rescan_remove();
|
||||
} else {
|
||||
eeh_for_each_pe(pe, tmp_pe)
|
||||
eeh_pe_for_each_dev(tmp_pe, edev, tmp_edev)
|
||||
|
@ -1199,7 +1215,6 @@ void eeh_handle_special_event(void)
|
|||
eeh_report_failure, NULL);
|
||||
eeh_set_channel_state(pe, pci_channel_io_perm_failure);
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
list_for_each_entry(hose, &hose_list, list_node) {
|
||||
phb_pe = eeh_phb_pe_get(hose);
|
||||
if (!phb_pe ||
|
||||
|
@ -1218,7 +1233,6 @@ void eeh_handle_special_event(void)
|
|||
}
|
||||
pci_hp_remove_devices(bus);
|
||||
}
|
||||
pci_unlock_rescan_remove();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1228,4 +1242,6 @@ void eeh_handle_special_event(void)
|
|||
if (rc == EEH_NEXT_ERR_DEAD_IOC)
|
||||
break;
|
||||
} while (rc != EEH_NEXT_ERR_NONE);
|
||||
|
||||
pci_unlock_rescan_remove();
|
||||
}
|
||||
|
|
|
@ -671,10 +671,12 @@ static void eeh_bridge_check_link(struct eeh_dev *edev)
|
|||
eeh_ops->write_config(edev, cap + PCI_EXP_LNKCTL, 2, val);
|
||||
|
||||
/* Check link */
|
||||
if (!edev->pdev->link_active_reporting) {
|
||||
eeh_edev_dbg(edev, "No link reporting capability\n");
|
||||
msleep(1000);
|
||||
return;
|
||||
if (edev->pdev) {
|
||||
if (!edev->pdev->link_active_reporting) {
|
||||
eeh_edev_dbg(edev, "No link reporting capability\n");
|
||||
msleep(1000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait the link is up until timeout (5s) */
|
||||
|
|
|
@ -141,6 +141,9 @@ void pci_hp_add_devices(struct pci_bus *bus)
|
|||
struct pci_controller *phb;
|
||||
struct device_node *dn = pci_bus_to_OF_node(bus);
|
||||
|
||||
if (!dn)
|
||||
return;
|
||||
|
||||
phb = pci_bus_to_host(bus);
|
||||
|
||||
mode = PCI_PROBE_NORMAL;
|
||||
|
|
|
@ -409,6 +409,71 @@ asm (
|
|||
" blr ;"
|
||||
);
|
||||
|
||||
static int emit_atomic_ld_st(const struct bpf_insn insn, struct codegen_context *ctx, u32 *image)
|
||||
{
|
||||
u32 code = insn.code;
|
||||
u32 dst_reg = bpf_to_ppc(insn.dst_reg);
|
||||
u32 src_reg = bpf_to_ppc(insn.src_reg);
|
||||
u32 size = BPF_SIZE(code);
|
||||
u32 tmp1_reg = bpf_to_ppc(TMP_REG_1);
|
||||
u32 tmp2_reg = bpf_to_ppc(TMP_REG_2);
|
||||
s16 off = insn.off;
|
||||
s32 imm = insn.imm;
|
||||
|
||||
switch (imm) {
|
||||
case BPF_LOAD_ACQ:
|
||||
switch (size) {
|
||||
case BPF_B:
|
||||
EMIT(PPC_RAW_LBZ(dst_reg, src_reg, off));
|
||||
break;
|
||||
case BPF_H:
|
||||
EMIT(PPC_RAW_LHZ(dst_reg, src_reg, off));
|
||||
break;
|
||||
case BPF_W:
|
||||
EMIT(PPC_RAW_LWZ(dst_reg, src_reg, off));
|
||||
break;
|
||||
case BPF_DW:
|
||||
if (off % 4) {
|
||||
EMIT(PPC_RAW_LI(tmp1_reg, off));
|
||||
EMIT(PPC_RAW_LDX(dst_reg, src_reg, tmp1_reg));
|
||||
} else {
|
||||
EMIT(PPC_RAW_LD(dst_reg, src_reg, off));
|
||||
}
|
||||
break;
|
||||
}
|
||||
EMIT(PPC_RAW_LWSYNC());
|
||||
break;
|
||||
case BPF_STORE_REL:
|
||||
EMIT(PPC_RAW_LWSYNC());
|
||||
switch (size) {
|
||||
case BPF_B:
|
||||
EMIT(PPC_RAW_STB(src_reg, dst_reg, off));
|
||||
break;
|
||||
case BPF_H:
|
||||
EMIT(PPC_RAW_STH(src_reg, dst_reg, off));
|
||||
break;
|
||||
case BPF_W:
|
||||
EMIT(PPC_RAW_STW(src_reg, dst_reg, off));
|
||||
break;
|
||||
case BPF_DW:
|
||||
if (off % 4) {
|
||||
EMIT(PPC_RAW_LI(tmp2_reg, off));
|
||||
EMIT(PPC_RAW_STDX(src_reg, dst_reg, tmp2_reg));
|
||||
} else {
|
||||
EMIT(PPC_RAW_STD(src_reg, dst_reg, off));
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pr_err_ratelimited("unexpected atomic load/store op code %02x\n",
|
||||
imm);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Assemble the body code between the prologue & epilogue */
|
||||
int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct codegen_context *ctx,
|
||||
u32 *addrs, int pass, bool extra_pass)
|
||||
|
@ -898,8 +963,25 @@ emit_clear:
|
|||
/*
|
||||
* BPF_STX ATOMIC (atomic ops)
|
||||
*/
|
||||
case BPF_STX | BPF_ATOMIC | BPF_B:
|
||||
case BPF_STX | BPF_ATOMIC | BPF_H:
|
||||
case BPF_STX | BPF_ATOMIC | BPF_W:
|
||||
case BPF_STX | BPF_ATOMIC | BPF_DW:
|
||||
if (bpf_atomic_is_load_store(&insn[i])) {
|
||||
ret = emit_atomic_ld_st(insn[i], ctx, image);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (size != BPF_DW && insn_is_zext(&insn[i + 1]))
|
||||
addrs[++i] = ctx->idx * 4;
|
||||
break;
|
||||
} else if (size == BPF_B || size == BPF_H) {
|
||||
pr_err_ratelimited(
|
||||
"eBPF filter atomic op code %02x (@%d) unsupported\n",
|
||||
code, i);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
save_reg = tmp2_reg;
|
||||
ret_reg = src_reg;
|
||||
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
* PCI Hotplug Driver for PowerPC PowerNV platform.
|
||||
*
|
||||
* Copyright Gavin Shan, IBM Corporation 2016.
|
||||
* Copyright (C) 2025 Raptor Engineering, LLC
|
||||
* Copyright (C) 2025 Raptor Computing Systems, LLC
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pci_hotplug.h>
|
||||
#include <linux/of_fdt.h>
|
||||
|
||||
|
@ -36,8 +39,10 @@ static void pnv_php_register(struct device_node *dn);
|
|||
static void pnv_php_unregister_one(struct device_node *dn);
|
||||
static void pnv_php_unregister(struct device_node *dn);
|
||||
|
||||
static void pnv_php_enable_irq(struct pnv_php_slot *php_slot);
|
||||
|
||||
static void pnv_php_disable_irq(struct pnv_php_slot *php_slot,
|
||||
bool disable_device)
|
||||
bool disable_device, bool disable_msi)
|
||||
{
|
||||
struct pci_dev *pdev = php_slot->pdev;
|
||||
u16 ctrl;
|
||||
|
@ -53,19 +58,15 @@ static void pnv_php_disable_irq(struct pnv_php_slot *php_slot,
|
|||
php_slot->irq = 0;
|
||||
}
|
||||
|
||||
if (php_slot->wq) {
|
||||
destroy_workqueue(php_slot->wq);
|
||||
php_slot->wq = NULL;
|
||||
}
|
||||
|
||||
if (disable_device) {
|
||||
if (disable_device || disable_msi) {
|
||||
if (pdev->msix_enabled)
|
||||
pci_disable_msix(pdev);
|
||||
else if (pdev->msi_enabled)
|
||||
pci_disable_msi(pdev);
|
||||
|
||||
pci_disable_device(pdev);
|
||||
}
|
||||
|
||||
if (disable_device)
|
||||
pci_disable_device(pdev);
|
||||
}
|
||||
|
||||
static void pnv_php_free_slot(struct kref *kref)
|
||||
|
@ -74,7 +75,8 @@ static void pnv_php_free_slot(struct kref *kref)
|
|||
struct pnv_php_slot, kref);
|
||||
|
||||
WARN_ON(!list_empty(&php_slot->children));
|
||||
pnv_php_disable_irq(php_slot, false);
|
||||
pnv_php_disable_irq(php_slot, false, false);
|
||||
destroy_workqueue(php_slot->wq);
|
||||
kfree(php_slot->name);
|
||||
kfree(php_slot);
|
||||
}
|
||||
|
@ -391,6 +393,20 @@ static int pnv_php_get_power_state(struct hotplug_slot *slot, u8 *state)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int pcie_check_link_active(struct pci_dev *pdev)
|
||||
{
|
||||
u16 lnk_status;
|
||||
int ret;
|
||||
|
||||
ret = pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
|
||||
if (ret == PCIBIOS_DEVICE_NOT_FOUND || PCI_POSSIBLE_ERROR(lnk_status))
|
||||
return -ENODEV;
|
||||
|
||||
ret = !!(lnk_status & PCI_EXP_LNKSTA_DLLLA);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pnv_php_get_adapter_state(struct hotplug_slot *slot, u8 *state)
|
||||
{
|
||||
struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
|
||||
|
@ -403,6 +419,19 @@ static int pnv_php_get_adapter_state(struct hotplug_slot *slot, u8 *state)
|
|||
*/
|
||||
ret = pnv_pci_get_presence_state(php_slot->id, &presence);
|
||||
if (ret >= 0) {
|
||||
if (pci_pcie_type(php_slot->pdev) == PCI_EXP_TYPE_DOWNSTREAM &&
|
||||
presence == OPAL_PCI_SLOT_EMPTY) {
|
||||
/*
|
||||
* Similar to pciehp_hpc, check whether the Link Active
|
||||
* bit is set to account for broken downstream bridges
|
||||
* that don't properly assert Presence Detect State, as
|
||||
* was observed on the Microsemi Switchtec PM8533 PFX
|
||||
* [11f8:8533].
|
||||
*/
|
||||
if (pcie_check_link_active(php_slot->pdev) > 0)
|
||||
presence = OPAL_PCI_SLOT_PRESENT;
|
||||
}
|
||||
|
||||
*state = presence;
|
||||
ret = 0;
|
||||
} else {
|
||||
|
@ -412,10 +441,23 @@ static int pnv_php_get_adapter_state(struct hotplug_slot *slot, u8 *state)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int pnv_php_get_raw_indicator_status(struct hotplug_slot *slot, u8 *state)
|
||||
{
|
||||
struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
|
||||
struct pci_dev *bridge = php_slot->pdev;
|
||||
u16 status;
|
||||
|
||||
pcie_capability_read_word(bridge, PCI_EXP_SLTCTL, &status);
|
||||
*state = (status & (PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC)) >> 6;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int pnv_php_get_attention_state(struct hotplug_slot *slot, u8 *state)
|
||||
{
|
||||
struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
|
||||
|
||||
pnv_php_get_raw_indicator_status(slot, &php_slot->attention_state);
|
||||
*state = php_slot->attention_state;
|
||||
return 0;
|
||||
}
|
||||
|
@ -433,7 +475,7 @@ static int pnv_php_set_attention_state(struct hotplug_slot *slot, u8 state)
|
|||
mask = PCI_EXP_SLTCTL_AIC;
|
||||
|
||||
if (state)
|
||||
new = PCI_EXP_SLTCTL_ATTN_IND_ON;
|
||||
new = FIELD_PREP(PCI_EXP_SLTCTL_AIC, state);
|
||||
else
|
||||
new = PCI_EXP_SLTCTL_ATTN_IND_OFF;
|
||||
|
||||
|
@ -442,6 +484,61 @@ static int pnv_php_set_attention_state(struct hotplug_slot *slot, u8 state)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int pnv_php_activate_slot(struct pnv_php_slot *php_slot,
|
||||
struct hotplug_slot *slot)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
/*
|
||||
* Issue initial slot activation command to firmware
|
||||
*
|
||||
* Firmware will power slot on, attempt to train the link, and
|
||||
* discover any downstream devices. If this process fails, firmware
|
||||
* will return an error code and an invalid device tree. Failure
|
||||
* can be caused for multiple reasons, including a faulty
|
||||
* downstream device, poor connection to the downstream device, or
|
||||
* a previously latched PHB fence. On failure, issue fundamental
|
||||
* reset up to three times before aborting.
|
||||
*/
|
||||
ret = pnv_php_set_slot_power_state(slot, OPAL_PCI_SLOT_POWER_ON);
|
||||
if (ret) {
|
||||
SLOT_WARN(
|
||||
php_slot,
|
||||
"PCI slot activation failed with error code %d, possible frozen PHB",
|
||||
ret);
|
||||
SLOT_WARN(
|
||||
php_slot,
|
||||
"Attempting complete PHB reset before retrying slot activation\n");
|
||||
for (i = 0; i < 3; i++) {
|
||||
/*
|
||||
* Slot activation failed, PHB may be fenced from a
|
||||
* prior device failure.
|
||||
*
|
||||
* Use the OPAL fundamental reset call to both try a
|
||||
* device reset and clear any potentially active PHB
|
||||
* fence / freeze.
|
||||
*/
|
||||
SLOT_WARN(php_slot, "Try %d...\n", i + 1);
|
||||
pci_set_pcie_reset_state(php_slot->pdev,
|
||||
pcie_warm_reset);
|
||||
msleep(250);
|
||||
pci_set_pcie_reset_state(php_slot->pdev,
|
||||
pcie_deassert_reset);
|
||||
|
||||
ret = pnv_php_set_slot_power_state(
|
||||
slot, OPAL_PCI_SLOT_POWER_ON);
|
||||
if (!ret)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i >= 3)
|
||||
SLOT_WARN(php_slot,
|
||||
"Failed to bring slot online, aborting!\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pnv_php_enable(struct pnv_php_slot *php_slot, bool rescan)
|
||||
{
|
||||
struct hotplug_slot *slot = &php_slot->slot;
|
||||
|
@ -504,7 +601,7 @@ static int pnv_php_enable(struct pnv_php_slot *php_slot, bool rescan)
|
|||
goto scan;
|
||||
|
||||
/* Power is off, turn it on and then scan the slot */
|
||||
ret = pnv_php_set_slot_power_state(slot, OPAL_PCI_SLOT_POWER_ON);
|
||||
ret = pnv_php_activate_slot(php_slot, slot);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -561,8 +658,58 @@ static int pnv_php_reset_slot(struct hotplug_slot *slot, bool probe)
|
|||
static int pnv_php_enable_slot(struct hotplug_slot *slot)
|
||||
{
|
||||
struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
|
||||
u32 prop32;
|
||||
int ret;
|
||||
|
||||
return pnv_php_enable(php_slot, true);
|
||||
ret = pnv_php_enable(php_slot, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* (Re-)enable interrupt if the slot supports surprise hotplug */
|
||||
ret = of_property_read_u32(php_slot->dn, "ibm,slot-surprise-pluggable",
|
||||
&prop32);
|
||||
if (!ret && prop32)
|
||||
pnv_php_enable_irq(php_slot);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable any hotplug interrupts for all slots on the provided bus, as well as
|
||||
* all downstream slots in preparation for a hot unplug.
|
||||
*/
|
||||
static int pnv_php_disable_all_irqs(struct pci_bus *bus)
|
||||
{
|
||||
struct pci_bus *child_bus;
|
||||
struct pci_slot *slot;
|
||||
|
||||
/* First go down child buses */
|
||||
list_for_each_entry(child_bus, &bus->children, node)
|
||||
pnv_php_disable_all_irqs(child_bus);
|
||||
|
||||
/* Disable IRQs for all pnv_php slots on this bus */
|
||||
list_for_each_entry(slot, &bus->slots, list) {
|
||||
struct pnv_php_slot *php_slot = to_pnv_php_slot(slot->hotplug);
|
||||
|
||||
pnv_php_disable_irq(php_slot, false, true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable any hotplug interrupts for all downstream slots on the provided
|
||||
* bus in preparation for a hot unplug.
|
||||
*/
|
||||
static int pnv_php_disable_all_downstream_irqs(struct pci_bus *bus)
|
||||
{
|
||||
struct pci_bus *child_bus;
|
||||
|
||||
/* Go down child buses, recursively deactivating their IRQs */
|
||||
list_for_each_entry(child_bus, &bus->children, node)
|
||||
pnv_php_disable_all_irqs(child_bus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pnv_php_disable_slot(struct hotplug_slot *slot)
|
||||
|
@ -579,6 +726,13 @@ static int pnv_php_disable_slot(struct hotplug_slot *slot)
|
|||
php_slot->state != PNV_PHP_STATE_REGISTERED)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Free all IRQ resources from all child slots before remove.
|
||||
* Note that we do not disable the root slot IRQ here as that
|
||||
* would also deactivate the slot hot (re)plug interrupt!
|
||||
*/
|
||||
pnv_php_disable_all_downstream_irqs(php_slot->bus);
|
||||
|
||||
/* Remove all devices behind the slot */
|
||||
pci_lock_rescan_remove();
|
||||
pci_hp_remove_devices(php_slot->bus);
|
||||
|
@ -647,6 +801,15 @@ static struct pnv_php_slot *pnv_php_alloc_slot(struct device_node *dn)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* Allocate workqueue for this slot's interrupt handling */
|
||||
php_slot->wq = alloc_workqueue("pciehp-%s", 0, 0, php_slot->name);
|
||||
if (!php_slot->wq) {
|
||||
SLOT_WARN(php_slot, "Cannot alloc workqueue\n");
|
||||
kfree(php_slot->name);
|
||||
kfree(php_slot);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (dn->child && PCI_DN(dn->child))
|
||||
php_slot->slot_no = PCI_SLOT(PCI_DN(dn->child)->devfn);
|
||||
else
|
||||
|
@ -745,16 +908,63 @@ static int pnv_php_enable_msix(struct pnv_php_slot *php_slot)
|
|||
return entry.vector;
|
||||
}
|
||||
|
||||
static void
|
||||
pnv_php_detect_clear_suprise_removal_freeze(struct pnv_php_slot *php_slot)
|
||||
{
|
||||
struct pci_dev *pdev = php_slot->pdev;
|
||||
struct eeh_dev *edev;
|
||||
struct eeh_pe *pe;
|
||||
int i, rc;
|
||||
|
||||
/*
|
||||
* When a device is surprise removed from a downstream bridge slot,
|
||||
* the upstream bridge port can still end up frozen due to related EEH
|
||||
* events, which will in turn block the MSI interrupts for slot hotplug
|
||||
* detection.
|
||||
*
|
||||
* Detect and thaw any frozen upstream PE after slot deactivation.
|
||||
*/
|
||||
edev = pci_dev_to_eeh_dev(pdev);
|
||||
pe = edev ? edev->pe : NULL;
|
||||
rc = eeh_pe_get_state(pe);
|
||||
if ((rc == -ENODEV) || (rc == -ENOENT)) {
|
||||
SLOT_WARN(
|
||||
php_slot,
|
||||
"Upstream bridge PE state unknown, hotplug detect may fail\n");
|
||||
} else {
|
||||
if (pe->state & EEH_PE_ISOLATED) {
|
||||
SLOT_WARN(
|
||||
php_slot,
|
||||
"Upstream bridge PE %02x frozen, thawing...\n",
|
||||
pe->addr);
|
||||
for (i = 0; i < 3; i++)
|
||||
if (!eeh_unfreeze_pe(pe))
|
||||
break;
|
||||
if (i >= 3)
|
||||
SLOT_WARN(
|
||||
php_slot,
|
||||
"Unable to thaw PE %02x, hotplug detect will fail!\n",
|
||||
pe->addr);
|
||||
else
|
||||
SLOT_WARN(php_slot,
|
||||
"PE %02x thawed successfully\n",
|
||||
pe->addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void pnv_php_event_handler(struct work_struct *work)
|
||||
{
|
||||
struct pnv_php_event *event =
|
||||
container_of(work, struct pnv_php_event, work);
|
||||
struct pnv_php_slot *php_slot = event->php_slot;
|
||||
|
||||
if (event->added)
|
||||
if (event->added) {
|
||||
pnv_php_enable_slot(&php_slot->slot);
|
||||
else
|
||||
} else {
|
||||
pnv_php_disable_slot(&php_slot->slot);
|
||||
pnv_php_detect_clear_suprise_removal_freeze(php_slot);
|
||||
}
|
||||
|
||||
kfree(event);
|
||||
}
|
||||
|
@ -843,14 +1053,6 @@ static void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq)
|
|||
u16 sts, ctrl;
|
||||
int ret;
|
||||
|
||||
/* Allocate workqueue */
|
||||
php_slot->wq = alloc_workqueue("pciehp-%s", 0, 0, php_slot->name);
|
||||
if (!php_slot->wq) {
|
||||
SLOT_WARN(php_slot, "Cannot alloc workqueue\n");
|
||||
pnv_php_disable_irq(php_slot, true);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check PDC (Presence Detection Change) is broken or not */
|
||||
ret = of_property_read_u32(php_slot->dn, "ibm,slot-broken-pdc",
|
||||
&broken_pdc);
|
||||
|
@ -869,7 +1071,7 @@ static void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq)
|
|||
ret = request_irq(irq, pnv_php_interrupt, IRQF_SHARED,
|
||||
php_slot->name, php_slot);
|
||||
if (ret) {
|
||||
pnv_php_disable_irq(php_slot, true);
|
||||
pnv_php_disable_irq(php_slot, true, true);
|
||||
SLOT_WARN(php_slot, "Error %d enabling IRQ %d\n", ret, irq);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -229,7 +229,8 @@
|
|||
|
||||
#if __clang_major__ >= 18 && defined(ENABLE_ATOMICS_TESTS) && \
|
||||
(defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86) || \
|
||||
(defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64))
|
||||
(defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64)) || \
|
||||
(defined(__TARGET_ARCH_powerpc))
|
||||
#define CAN_USE_LOAD_ACQ_STORE_REL
|
||||
#endif
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue