Add an API function, pcie_tph_set_st(), to allow endpoint device driver to update the steering tags. Depending on ST table location, the tags will be written into device's MSI-X table or TPH Requester Extended Capability structure. Co-developed-by: Eric Van Tassell <Eric.VanTassell@xxxxxxx> Signed-off-by: Eric Van Tassell <Eric.VanTassell@xxxxxxx> Signed-off-by: Wei Huang <wei.huang2@xxxxxxx> Reviewed-by: Ajit Khaparde <ajit.khaparde@xxxxxxxxxxxx> Reviewed-by: Somnath Kotur <somnath.kotur@xxxxxxxxxxxx> Reviewed-by: Andy Gospodarek <andrew.gospodarek@xxxxxxxxxxxx> --- drivers/pci/pcie/tph.c | 190 ++++++++++++++++++++++++++++++++++++++++ include/linux/pci-tph.h | 7 ++ 2 files changed, 197 insertions(+) diff --git a/drivers/pci/pcie/tph.c b/drivers/pci/pcie/tph.c index c805c8b1a7d2..8a0e48c913cf 100644 --- a/drivers/pci/pcie/tph.c +++ b/drivers/pci/pcie/tph.c @@ -13,6 +13,7 @@ #include <linux/pci.h> #include <linux/pci-acpi.h> #include <linux/bitfield.h> +#include <linux/msi.h> #include <linux/pci-tph.h> #include "../pci.h" @@ -171,6 +172,160 @@ static bool int_vec_mode_supported(struct pci_dev *pdev) return !!mode; } +static u32 get_st_table_loc(struct pci_dev *pdev) +{ + u32 reg_val; + + pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CAP, ®_val); + + return FIELD_GET(PCI_TPH_CAP_LOC_MASK, reg_val); +} + +static bool msix_index_in_bound(struct pci_dev *pdev, int msi_idx) +{ + u32 reg_val; + u16 st_tbl_sz; + + pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CAP, ®_val); + st_tbl_sz = FIELD_GET(PCI_TPH_CAP_ST_MASK, reg_val); + + return msi_idx <= st_tbl_sz; +} + +/* Write ST to MSI-X vector control reg - Return 0 if OK, otherwise errno */ +static int tph_write_tag_to_msix(struct pci_dev *pdev, int msi_idx, u16 tag) +{ + struct msi_desc *msi_desc = NULL; + void __iomem *vec_ctrl; + u32 val; + int err = 0; + + if (!msix_index_in_bound(pdev, msi_idx)) + return -EINVAL; + + msi_lock_descs(&pdev->dev); + + /* find the msi_desc entry with matching msi_idx */ + msi_for_each_desc(msi_desc, &pdev->dev, MSI_DESC_ASSOCIATED) { + if (msi_desc->msi_index == msi_idx) + break; + } + + if (!msi_desc) { + pci_err(pdev, "MSI-X descriptor for #%d not found\n", msi_idx); + err = -ENXIO; + goto err_out; + } + + /* get the vector control register (offset 0xc) pointed by msi_idx */ + vec_ctrl = pdev->msix_base + msi_idx * PCI_MSIX_ENTRY_SIZE; + vec_ctrl += PCI_MSIX_ENTRY_VECTOR_CTRL; + + val = readl(vec_ctrl); + val &= 0xffff; + val |= (tag << 16); + writel(val, vec_ctrl); + + /* read back to flush the update */ + val = readl(vec_ctrl); + +err_out: + msi_unlock_descs(&pdev->dev); + return err; +} + +/* Return root port TPH completer capability - 0 means none */ +static u8 get_rp_completer_support(struct pci_dev *pdev) +{ + struct pci_dev *rp; + u32 reg_val; + int ret; + + rp = pcie_find_root_port(pdev); + if (!rp) { + pci_err(pdev, "cannot find root port of %s\n", dev_name(&pdev->dev)); + return 0; + } + + ret = pcie_capability_read_dword(rp, PCI_EXP_DEVCAP2, ®_val); + if (ret) { + pci_err(pdev, "cannot read device capabilities 2\n"); + return 0; + } + + return FIELD_GET(PCI_EXP_DEVCAP2_TPH_COMP_MASK, reg_val); +} + +/* + * TPH device needs to be below a rootport with the TPH Completer and + * the completer must offer a compatible level of completer support to that + * requested by the device driver. + */ +static bool rp_completer_support_ok(struct pci_dev *pdev, u8 req_cap) +{ + u8 rp_cap; + + rp_cap = get_rp_completer_support(pdev); + + if (req_cap > rp_cap) { + pci_err(pdev, "root port lacks proper TPH completer capability\n"); + return false; + } + + return true; +} + +/* Return 0 if OK, otherwise errno on failure */ +static int pcie_tph_write_st(struct pci_dev *pdev, unsigned int msix_idx, + u8 req_type, u16 tag) +{ + int offset; + u32 loc; + int err = 0; + + /* setting ST isn't needed - not an error, just return OK */ + if (!pdev->tph_cap || pci_tph_disabled() || pci_tph_nostmode() || + !pdev->msix_enabled || !int_vec_mode_supported(pdev)) + return 0; + + /* setting ST is incorrect in the following cases - return error */ + if (!msix_index_in_bound(pdev, msix_idx) || !rp_completer_support_ok(pdev, req_type)) + return -EINVAL; + + /* + * disable TPH before updating the tag to avoid potential instability + * as cautioned in PCIE Base Spec r6.2, sect 6.17.3 "ST Modes of Operation" + */ + pcie_tph_disable(pdev); + + loc = get_st_table_loc(pdev); + /* Note: use FIELD_PREP to match PCI_TPH_LOC_* definitions in header */ + loc = FIELD_PREP(PCI_TPH_CAP_LOC_MASK, loc); + + switch (loc) { + case PCI_TPH_LOC_MSIX: + err = tph_write_tag_to_msix(pdev, msix_idx, tag); + break; + case PCI_TPH_LOC_CAP: + offset = pdev->tph_cap + PCI_TPH_BASE_SIZEOF + msix_idx * sizeof(u16); + err = pci_write_config_word(pdev, offset, tag); + break; + default: + pci_err(pdev, "unable to write steering tag for device %s\n", + dev_name(&pdev->dev)); + err = -EINVAL; + break; + } + + if (!err) { + /* re-enable interrupt vector mode */ + set_ctrl_reg_mode_sel(pdev, PCI_TPH_INT_VEC_MODE); + set_ctrl_reg_req_en(pdev, req_type); + } + + return err; +} + void pcie_tph_set_nostmode(struct pci_dev *pdev) { if (!pdev->tph_cap) @@ -251,3 +406,38 @@ int pcie_tph_get_st_from_acpi(struct pci_dev *pdev, unsigned int cpu_acpi_uid, return 0; } EXPORT_SYMBOL(pcie_tph_get_st_from_acpi); + +/** + * pcie_tph_set_st() - Set steering tag in ST table entry + * @pdev: pci device + * @msix_idx: ordinal number of msix interrupt. + * @cpu_acpi_uid: the acpi cpu_uid. + * @mem_type: memory type (vram, nvram) + * @req_type: request type (disable, tph, extended tph) + * + * Return: 0 if success, otherwise errno + */ +int pcie_tph_set_st(struct pci_dev *pdev, unsigned int msix_idx, + unsigned int cpu_acpi_uid, enum tph_mem_type mem_type, + u8 req_type) +{ + u16 tag; + int err = 0; + + if (!pdev->tph_cap) + return -ENODEV; + + err = pcie_tph_get_st_from_acpi(pdev, cpu_acpi_uid, mem_type, + req_type, &tag); + + if (err) + return err; + + pci_dbg(pdev, "%s: writing tag %d for msi-x intr %d (cpu: %d)\n", + __func__, tag, msix_idx, cpu_acpi_uid); + + err = pcie_tph_write_st(pdev, msix_idx, req_type, tag); + + return err; +} +EXPORT_SYMBOL(pcie_tph_set_st); diff --git a/include/linux/pci-tph.h b/include/linux/pci-tph.h index b12a592f3d49..1cc99cc528bd 100644 --- a/include/linux/pci-tph.h +++ b/include/linux/pci-tph.h @@ -21,6 +21,9 @@ bool pcie_tph_intr_vec_supported(struct pci_dev *dev); int pcie_tph_get_st_from_acpi(struct pci_dev *dev, unsigned int cpu_acpi_uid, enum tph_mem_type tag_type, u8 req_enable, u16 *tag); +int pcie_tph_set_st(struct pci_dev *dev, unsigned int msix_nr, + unsigned int cpu, enum tph_mem_type tag_type, + u8 req_enable); #else static inline void pcie_tph_disable(struct pci_dev *dev) {} static inline void pcie_tph_set_nostmode(struct pci_dev *dev) {} @@ -30,6 +33,10 @@ static inline int pcie_tph_get_st_from_acpi(struct pci_dev *dev, unsigned int cp enum tph_mem_type tag_type, u8 req_enable, u16 *tag) { return false; } +static inline int pcie_tph_set_st(struct pci_dev *dev, unsigned int msix_nr, + unsigned int cpu, enum tph_mem_type tag_type, + u8 req_enable) +{ return false; } #endif #endif /* LINUX_PCI_TPH_H */ -- 2.45.1