Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 63 additions & 11 deletions drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
Original file line number Diff line number Diff line change
Expand Up @@ -1423,7 +1423,7 @@ void arm_smmu_clear_cd(struct arm_smmu_master *master, ioasid_t ssid)
if (!arm_smmu_cdtab_allocated(&master->cd_table))
return;
cdptr = arm_smmu_get_cd_ptr(master, ssid);
if (WARN_ON(!cdptr))
if (!cdptr)
return;
arm_smmu_write_cd_entry(master, ssid, cdptr, &target);
}
Expand All @@ -1437,6 +1437,22 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master)
struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;

cd_table->s1cdmax = master->ssid_bits;

/*
* When a device doesn't support PASID (non default SSID), ssid_bits is
* set to 0. This also sets S1CDMAX to 0, which disables the substreams
* and ignores the S1DSS field.
*
* On the other hand, if a device demands ATS to be always on even when
* its default substream is IOMMU bypassed, it has to use EATS that is
* only effective with an STE (CFG=S1translate, S1DSS=Bypass). For such
* use cases, S1CDMAX has to be !0, in order to make use of S1DSS/EATS.
*
* Set S1CDMAX no lower than 1. This would add a dummy substream in the
* CD table but it should never be used by an actual CD.
*/
if (master->ats_always_on)
cd_table->s1cdmax = max_t(u8, cd_table->s1cdmax, 1);
max_contexts = 1 << cd_table->s1cdmax;

if (!(smmu->features & ARM_SMMU_FEAT_2_LVL_CDTAB) ||
Expand Down Expand Up @@ -3189,7 +3205,8 @@ static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
* When the last user of the CD table goes away downgrade the STE back
* to a non-cd_table one.
*/
if (!arm_smmu_ssids_in_use(&master->cd_table)) {
if (!master->ats_always_on &&
!arm_smmu_ssids_in_use(&master->cd_table)) {
struct iommu_domain *sid_domain =
iommu_get_domain_for_dev(master->dev);

Expand All @@ -3203,7 +3220,7 @@ static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
static void arm_smmu_attach_dev_ste(struct iommu_domain *domain,
struct device *dev,
struct arm_smmu_ste *ste,
unsigned int s1dss)
unsigned int s1dss, bool ats_always_on)
{
struct arm_smmu_master *master = dev_iommu_priv_get(dev);
struct arm_smmu_attach_state state = {
Expand All @@ -3222,7 +3239,7 @@ static void arm_smmu_attach_dev_ste(struct iommu_domain *domain,
* If the CD table is not in use we can use the provided STE, otherwise
* we use a cdtable STE with the provided S1DSS.
*/
if (arm_smmu_ssids_in_use(&master->cd_table)) {
if (ats_always_on || arm_smmu_ssids_in_use(&master->cd_table)) {
/*
* If a CD table has to be present then we need to run with ATS
* on because we have to assume a PASID is using ATS. For
Expand Down Expand Up @@ -3256,7 +3273,8 @@ static int arm_smmu_attach_dev_identity(struct iommu_domain *domain,

arm_smmu_master_clear_vmaster(master);
arm_smmu_make_bypass_ste(master->smmu, &ste);
arm_smmu_attach_dev_ste(domain, dev, &ste, STRTAB_STE_1_S1DSS_BYPASS);
arm_smmu_attach_dev_ste(domain, dev, &ste, STRTAB_STE_1_S1DSS_BYPASS,
master->ats_always_on);
return 0;
}

Expand All @@ -3278,7 +3296,7 @@ static int arm_smmu_attach_dev_blocked(struct iommu_domain *domain,
arm_smmu_master_clear_vmaster(master);
arm_smmu_make_abort_ste(&ste);
arm_smmu_attach_dev_ste(domain, dev, &ste,
STRTAB_STE_1_S1DSS_TERMINATE);
STRTAB_STE_1_S1DSS_TERMINATE, false);
return 0;
}

Expand Down Expand Up @@ -3516,6 +3534,40 @@ static void arm_smmu_remove_master(struct arm_smmu_master *master)
kfree(master->streams);
}

static int arm_smmu_master_prepare_ats(struct arm_smmu_master *master)
{
bool s1p = master->smmu->features & ARM_SMMU_FEAT_TRANS_S1;
unsigned int stu = __ffs(master->smmu->pgsize_bitmap);
struct pci_dev *pdev = to_pci_dev(master->dev);
int ret;

if (!arm_smmu_ats_supported(master))
return 0;

if (!pci_ats_always_on(pdev))
goto out_prepare;

/*
* S1DSS is required for ATS to be always on for identity domain cases.
* However, the S1DSS field is ignored if !IDR0_S1P or !IDR1_SSIDSIZE.
*/
if (!s1p || !master->smmu->ssid_bits) {
dev_info_once(master->dev,
"SMMU doesn't support ATS to be always on\n");
goto out_prepare;
}

master->ats_always_on = true;

ret = arm_smmu_alloc_cd_tables(master);
if (ret)
return ret;

out_prepare:
pci_prepare_ats(pdev, stu);
return 0;
}

static struct iommu_device *arm_smmu_probe_device(struct device *dev)
{
int ret;
Expand Down Expand Up @@ -3564,14 +3616,14 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev)
smmu->features & ARM_SMMU_FEAT_STALL_FORCE)
master->stall_enabled = true;

if (dev_is_pci(dev)) {
unsigned int stu = __ffs(smmu->pgsize_bitmap);

pci_prepare_ats(to_pci_dev(dev), stu);
}
ret = arm_smmu_master_prepare_ats(master);
if (ret)
goto err_disable_pasid;

return &smmu->iommu;

err_disable_pasid:
arm_smmu_disable_pasid(master);
err_free_master:
kfree(master);
return ERR_PTR(ret);
Expand Down
1 change: 1 addition & 0 deletions drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,7 @@ struct arm_smmu_master {
bool ats_enabled : 1;
bool ste_ats_enabled : 1;
bool stall_enabled;
bool ats_always_on;
unsigned int ssid_bits;
unsigned int iopf_refcount;
u16 partid;
Expand Down
45 changes: 45 additions & 0 deletions drivers/pci/ats.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,51 @@ int pci_ats_page_aligned(struct pci_dev *pdev)
return 0;
}

/*
* CXL r4.0, sec 3.2.5.13 Memory Type on CXL.cache notes: to source requests on
* CXL.cache, devices need to get the Host Physical Address (HPA) from the Host
* by means of an ATS request on CXL.io.
*
* In other world, CXL.cache devices cannot access physical memory without ATS.
*/
static bool pci_cxl_ats_always_on(struct pci_dev *pdev)
{
int offset;
u16 cap;

offset = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
CXL_DVSEC_PCIE_DEVICE);
if (!offset)
return false;

pci_read_config_word(pdev, offset + CXL_DVSEC_CAP_OFFSET, &cap);
if (cap & CXL_DVSEC_CACHE_CAPABLE)
return true;

return false;
}

/**
* pci_ats_always_on - Whether the PCI device requires ATS to be always enabled
* @pdev: the PCI device
*
* Returns true, if the PCI device requires non-PASID ATS function on an IOMMU
* bypassed configuration.
*/
bool pci_ats_always_on(struct pci_dev *pdev)
{
if (pci_ats_disabled() || !pci_ats_supported(pdev))
return false;

/* A VF inherits its PF's requirement for ATS function */
if (pdev->is_virtfn)
pdev = pci_physfn(pdev);

return pci_cxl_ats_always_on(pdev) ||
pci_dev_specific_ats_always_on(pdev);
}
EXPORT_SYMBOL_GPL(pci_ats_always_on);

#ifdef CONFIG_PCI_PRI
void pci_pri_init(struct pci_dev *pdev)
{
Expand Down
9 changes: 9 additions & 0 deletions drivers/pci/pci.h
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,15 @@ static inline int pci_dev_specific_reset(struct pci_dev *dev, bool probe)
}
#endif

#if defined(CONFIG_PCI_QUIRKS) && defined(CONFIG_PCI_ATS)
bool pci_dev_specific_ats_always_on(struct pci_dev *dev);
#else
static inline bool pci_dev_specific_ats_always_on(struct pci_dev *dev)
{
return false;
}
#endif

#if defined(CONFIG_PCI_QUIRKS) && defined(CONFIG_ARM64)
int acpi_get_rc_resources(struct device *dev, const char *hid, u16 segment,
struct resource *res);
Expand Down
23 changes: 23 additions & 0 deletions drivers/pci/quirks.c
Original file line number Diff line number Diff line change
Expand Up @@ -5678,6 +5678,29 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x1457, quirk_intel_e2000_no_ats);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x1459, quirk_intel_e2000_no_ats);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x145a, quirk_intel_e2000_no_ats);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x145c, quirk_intel_e2000_no_ats);

static const struct pci_dev_ats_always_on {
u16 vendor;
u16 device;
} pci_dev_ats_always_on[] = {
{ PCI_VENDOR_ID_NVIDIA, 0x2e12, },
{ PCI_VENDOR_ID_NVIDIA, 0x2e2a, },
{ PCI_VENDOR_ID_NVIDIA, 0x2e2b, },
{ 0 }
};

/* Some non-CXL devices support ATS on RID when it is IOMMU-bypassed */
bool pci_dev_specific_ats_always_on(struct pci_dev *pdev)
{
const struct pci_dev_ats_always_on *i;

for (i = pci_dev_ats_always_on; i->vendor; i++) {
if (i->vendor == pdev->vendor && i->device == pdev->device)
return true;
}

return false;
}
#endif /* CONFIG_PCI_ATS */

/* Freescale PCIe doesn't support MSI in RC mode */
Expand Down
3 changes: 3 additions & 0 deletions include/linux/pci-ats.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ int pci_prepare_ats(struct pci_dev *dev, int ps);
void pci_disable_ats(struct pci_dev *dev);
int pci_ats_queue_depth(struct pci_dev *dev);
int pci_ats_page_aligned(struct pci_dev *dev);
bool pci_ats_always_on(struct pci_dev *dev);
#else /* CONFIG_PCI_ATS */
static inline bool pci_ats_supported(struct pci_dev *d)
{ return false; }
Expand All @@ -24,6 +25,8 @@ static inline int pci_ats_queue_depth(struct pci_dev *d)
{ return -ENODEV; }
static inline int pci_ats_page_aligned(struct pci_dev *dev)
{ return 0; }
static inline bool pci_ats_always_on(struct pci_dev *dev)
{ return false; }
#endif /* CONFIG_PCI_ATS */

#ifdef CONFIG_PCI_PRI
Expand Down
5 changes: 5 additions & 0 deletions include/uapi/linux/pci_regs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1239,4 +1239,9 @@
#define PCI_DVSEC_CXL_PORT_CTL 0x0c
#define PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR 0x00000001

/* CXL 2.0 8.1.3: PCIe DVSEC for CXL Device */
#define CXL_DVSEC_PCIE_DEVICE 0
#define CXL_DVSEC_CAP_OFFSET 0xA
#define CXL_DVSEC_CACHE_CAPABLE BIT(0)

#endif /* LINUX_PCI_REGS_H */