Allow another driver to provide alternative operations when doing capability register reads and writes. The intention is to have pcie_bridge_emul provide alternate handlers for the Slot Capabilities, Slot Control, and Slot Status registers. Alternate handlers can return > 0 if unhandled, errno on error, or 0 on success. This could potentially be used to handle quirks in a different manner. Signed-off-by: Jonathan Derrick <jonathan.derrick@xxxxxxxxx> --- drivers/pci/access.c | 29 +++++++++++++++++++++++++++++ include/linux/pci.h | 11 +++++++++++ 2 files changed, 40 insertions(+) diff --git a/drivers/pci/access.c b/drivers/pci/access.c index 708c7529647f..dbfea6824bd4 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c @@ -424,6 +424,17 @@ int pcie_capability_read_word(struct pci_dev *dev, int pos, u16 *val) return ret; } + if (dev->caps_rw_ops) { + u32 reg; + ret = dev->caps_rw_ops->read(dev, pos, 4, ®); + if (!ret) { + *val = reg & 0xffff; + return ret; + } else if (ret < 0) { + return ret; + } + } + /* * For Functions that do not implement the Slot Capabilities, * Slot Status, and Slot Control registers, these spaces must @@ -459,6 +470,12 @@ int pcie_capability_read_dword(struct pci_dev *dev, int pos, u32 *val) return ret; } + if (dev->caps_rw_ops) { + ret = dev->caps_rw_ops->read(dev, pos, 4, val); + if (ret <= 0) + return ret; + } + if (pci_is_pcie(dev) && pcie_downstream_port(dev) && pos == PCI_EXP_SLTSTA) *val = PCI_EXP_SLTSTA_PDS; @@ -475,6 +492,12 @@ int pcie_capability_write_word(struct pci_dev *dev, int pos, u16 val) if (!pcie_capability_reg_implemented(dev, pos)) return 0; + if (dev->caps_rw_ops) { + int ret = dev->caps_rw_ops->write(dev, pos, 2, val); + if (ret <= 0) + return ret; + } + return pci_write_config_word(dev, pci_pcie_cap(dev) + pos, val); } EXPORT_SYMBOL(pcie_capability_write_word); @@ -487,6 +510,12 @@ int pcie_capability_write_dword(struct pci_dev *dev, int pos, u32 val) if (!pcie_capability_reg_implemented(dev, pos)) return 0; + if (dev->caps_rw_ops) { + int ret = dev->caps_rw_ops->write(dev, pos, 4, val); + if (ret <= 0) + return ret; + } + return pci_write_config_dword(dev, pci_pcie_cap(dev) + pos, val); } EXPORT_SYMBOL(pcie_capability_write_dword); diff --git a/include/linux/pci.h b/include/linux/pci.h index 2bda4a4e47e8..ff47ef83ab38 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -311,6 +311,15 @@ struct pci_vpd { u8 cap; }; +/* + * Capability reads/write redirect + * Returns 0, errno, or > 0 if unhandled + */ +struct caps_rw_ops { + int (*read)(struct pci_dev *dev, int pos, int len, u32 *val); + int (*write)(struct pci_dev *dev, int pos, int len, u32 val); +}; + struct irq_affinity; struct pcie_link_state; struct pci_sriov; @@ -523,6 +532,8 @@ struct pci_dev { /* These methods index pci_reset_fn_methods[] */ u8 reset_methods[PCI_NUM_RESET_METHODS]; /* In priority order */ + + struct caps_rw_ops *caps_rw_ops; }; static inline struct pci_dev *pci_physfn(struct pci_dev *dev) -- 2.30.2