powerpc/powernv: Support PCI config restore for VFs
authorWei Yang <weiyang@linux.vnet.ibm.com>
Thu, 3 Mar 2016 23:53:10 +0000 (10:53 +1100)
committerMichael Ellerman <mpe@ellerman.id.au>
Tue, 8 Mar 2016 22:58:22 +0000 (09:58 +1100)
After PE reset, OPAL API opal_pci_reinit() is called on all devices
contained in the PE to reinitialize them. While skiboot is not aware of
VFs, we have to implement the function in kernel to reinitialize VFs after
reset on PE for VFs.

In this patch, two functions pnv_pci_fixup_vf_mps() and
pnv_eeh_restore_vf_config() both manipulate the MPS of the VF, since for a
VF it has three cases.

1. Normal creation for a VF
   In this case, pnv_pci_fixup_vf_mps() is called to make the MPS a proper
   value compared with its parent.
2. EEH recovery without VF removed
   In this case, MPS is stored in pci_dn and pnv_eeh_restore_vf_config() is
   called to restore it and reinitialize other part.
3. EEH recovery with VF removed
   In this case, VF will be removed then re-created. Both functions are
   called. First pnv_pci_fixup_vf_mps() is called to store the proper MPS
   to pci_dn and then pnv_eeh_restore_vf_config() is called to do proper
   thing.

This introduces two functions: pnv_pci_fixup_vf_mps() to fixup the VF's
MPS to make sure it is equal to parent's and store this value in pci_dn
for future use. pnv_eeh_restore_vf_config() to re-initialize on VF by
restoring MPS, disabling completion timeout, enabling SERR, etc.

Signed-off-by: Wei Yang <weiyang@linux.vnet.ibm.com>
Acked-by: Gavin Shan <gwshan@linux.vnet.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
arch/powerpc/include/asm/pci-bridge.h
arch/powerpc/platforms/powernv/eeh-powernv.c

index b0b43f5..f4d1758 100644 (file)
@@ -220,6 +220,7 @@ struct pci_dn {
 #define IODA_INVALID_M64        (-1)
        int     (*m64_map)[PCI_SRIOV_NUM_BARS];
 #endif /* CONFIG_PCI_IOV */
+       int     mps;                    /* Maximum Payload Size */
 #endif
        struct list_head child_list;
        struct list_head list;
index e26256b..950b3e5 100644 (file)
@@ -1588,6 +1588,65 @@ static int pnv_eeh_next_error(struct eeh_pe **pe)
        return ret;
 }
 
+static int pnv_eeh_restore_vf_config(struct pci_dn *pdn)
+{
+       struct eeh_dev *edev = pdn_to_eeh_dev(pdn);
+       u32 devctl, cmd, cap2, aer_capctl;
+       int old_mps;
+
+       if (edev->pcie_cap) {
+               /* Restore MPS */
+               old_mps = (ffs(pdn->mps) - 8) << 5;
+               eeh_ops->read_config(pdn, edev->pcie_cap + PCI_EXP_DEVCTL,
+                                    2, &devctl);
+               devctl &= ~PCI_EXP_DEVCTL_PAYLOAD;
+               devctl |= old_mps;
+               eeh_ops->write_config(pdn, edev->pcie_cap + PCI_EXP_DEVCTL,
+                                     2, devctl);
+
+               /* Disable Completion Timeout */
+               eeh_ops->read_config(pdn, edev->pcie_cap + PCI_EXP_DEVCAP2,
+                                    4, &cap2);
+               if (cap2 & 0x10) {
+                       eeh_ops->read_config(pdn,
+                                            edev->pcie_cap + PCI_EXP_DEVCTL2,
+                                            4, &cap2);
+                       cap2 |= 0x10;
+                       eeh_ops->write_config(pdn,
+                                             edev->pcie_cap + PCI_EXP_DEVCTL2,
+                                             4, cap2);
+               }
+       }
+
+       /* Enable SERR and parity checking */
+       eeh_ops->read_config(pdn, PCI_COMMAND, 2, &cmd);
+       cmd |= (PCI_COMMAND_PARITY | PCI_COMMAND_SERR);
+       eeh_ops->write_config(pdn, PCI_COMMAND, 2, cmd);
+
+       /* Enable report various errors */
+       if (edev->pcie_cap) {
+               eeh_ops->read_config(pdn, edev->pcie_cap + PCI_EXP_DEVCTL,
+                                    2, &devctl);
+               devctl &= ~PCI_EXP_DEVCTL_CERE;
+               devctl |= (PCI_EXP_DEVCTL_NFERE |
+                          PCI_EXP_DEVCTL_FERE |
+                          PCI_EXP_DEVCTL_URRE);
+               eeh_ops->write_config(pdn, edev->pcie_cap + PCI_EXP_DEVCTL,
+                                     2, devctl);
+       }
+
+       /* Enable ECRC generation and check */
+       if (edev->pcie_cap && edev->aer_cap) {
+               eeh_ops->read_config(pdn, edev->aer_cap + PCI_ERR_CAP,
+                                    4, &aer_capctl);
+               aer_capctl |= (PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE);
+               eeh_ops->write_config(pdn, edev->aer_cap + PCI_ERR_CAP,
+                                     4, aer_capctl);
+       }
+
+       return 0;
+}
+
 static int pnv_eeh_restore_config(struct pci_dn *pdn)
 {
        struct eeh_dev *edev = pdn_to_eeh_dev(pdn);
@@ -1597,9 +1656,21 @@ static int pnv_eeh_restore_config(struct pci_dn *pdn)
        if (!edev)
                return -EEXIST;
 
-       phb = edev->phb->private_data;
-       ret = opal_pci_reinit(phb->opal_id,
-                             OPAL_REINIT_PCI_DEV, edev->config_addr);
+       /*
+        * We have to restore the PCI config space after reset since the
+        * firmware can't see SRIOV VFs.
+        *
+        * FIXME: The MPS, error routing rules, timeout setting are worthy
+        * to be exported by firmware in extendible way.
+        */
+       if (edev->physfn) {
+               ret = pnv_eeh_restore_vf_config(pdn);
+       } else {
+               phb = edev->phb->private_data;
+               ret = opal_pci_reinit(phb->opal_id,
+                                     OPAL_REINIT_PCI_DEV, edev->config_addr);
+       }
+
        if (ret) {
                pr_warn("%s: Can't reinit PCI dev 0x%x (%lld)\n",
                        __func__, edev->config_addr, ret);
@@ -1644,6 +1715,24 @@ void pcibios_bus_add_device(struct pci_dev *pdev)
        eeh_sysfs_add_device(pdev);
 }
 
+#ifdef CONFIG_PCI_IOV
+static void pnv_pci_fixup_vf_mps(struct pci_dev *pdev)
+{
+       struct pci_dn *pdn = pci_get_pdn(pdev);
+       int parent_mps;
+
+       if (!pdev->is_virtfn)
+               return;
+
+       /* Synchronize MPS for VF and PF */
+       parent_mps = pcie_get_mps(pdev->physfn);
+       if ((128 << pdev->pcie_mpss) >= parent_mps)
+               pcie_set_mps(pdev, parent_mps);
+       pdn->mps = pcie_get_mps(pdev);
+}
+DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, pnv_pci_fixup_vf_mps);
+#endif /* CONFIG_PCI_IOV */
+
 /**
  * eeh_powernv_init - Register platform dependent EEH operations
  *