816 lines
21 KiB
C
Executable File
816 lines
21 KiB
C
Executable File
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/acpi.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pci.h>
|
|
|
|
#include "arm-smmu-v3.h"
|
|
#include "../../dma-iommu.h"
|
|
|
|
struct arm_smmu_option_prop {
|
|
u32 opt;
|
|
const char *prop;
|
|
};
|
|
|
|
static struct arm_smmu_option_prop arm_smmu_options[] = {
|
|
{ ARM_SMMU_OPT_SKIP_PREFETCH, "hisilicon,broken-prefetch-cmd" },
|
|
{ ARM_SMMU_OPT_PAGE0_REGS_ONLY, "cavium,cn9900-broken-page1-regspace"},
|
|
{ 0, NULL},
|
|
};
|
|
|
|
static phys_addr_t arm_smmu_msi_cfg[ARM_SMMU_MAX_MSIS][3] = {
|
|
[EVTQ_MSI_INDEX] = {
|
|
ARM_SMMU_EVTQ_IRQ_CFG0,
|
|
ARM_SMMU_EVTQ_IRQ_CFG1,
|
|
ARM_SMMU_EVTQ_IRQ_CFG2,
|
|
},
|
|
[GERROR_MSI_INDEX] = {
|
|
ARM_SMMU_GERROR_IRQ_CFG0,
|
|
ARM_SMMU_GERROR_IRQ_CFG1,
|
|
ARM_SMMU_GERROR_IRQ_CFG2,
|
|
},
|
|
[PRIQ_MSI_INDEX] = {
|
|
ARM_SMMU_PRIQ_IRQ_CFG0,
|
|
ARM_SMMU_PRIQ_IRQ_CFG1,
|
|
ARM_SMMU_PRIQ_IRQ_CFG2,
|
|
},
|
|
};
|
|
|
|
static void parse_driver_options(struct arm_smmu_device *smmu)
|
|
{
|
|
int i = 0;
|
|
|
|
do {
|
|
if (of_property_read_bool(smmu->dev->of_node,
|
|
arm_smmu_options[i].prop)) {
|
|
smmu->options |= arm_smmu_options[i].opt;
|
|
dev_notice(smmu->dev, "option %s\n",
|
|
arm_smmu_options[i].prop);
|
|
}
|
|
} while (arm_smmu_options[++i].opt);
|
|
}
|
|
|
|
static int arm_smmu_device_dt_probe(struct platform_device *pdev,
|
|
struct arm_smmu_device *smmu,
|
|
bool *bypass)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
u32 cells;
|
|
|
|
*bypass = true;
|
|
if (of_property_read_u32(dev->of_node, "#iommu-cells", &cells))
|
|
dev_err(dev, "missing #iommu-cells property\n");
|
|
else if (cells != 1)
|
|
dev_err(dev, "invalid #iommu-cells value (%d)\n", cells);
|
|
else
|
|
*bypass = false;
|
|
|
|
parse_driver_options(smmu);
|
|
|
|
if (of_dma_is_coherent(dev->of_node))
|
|
smmu->features |= ARM_SMMU_FEAT_COHERENCY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_ACPI
|
|
static void acpi_smmu_get_options(u32 model, struct arm_smmu_device *smmu)
|
|
{
|
|
switch (model) {
|
|
case ACPI_IORT_SMMU_V3_CAVIUM_CN99XX:
|
|
smmu->options |= ARM_SMMU_OPT_PAGE0_REGS_ONLY;
|
|
break;
|
|
case ACPI_IORT_SMMU_V3_HISILICON_HI161X:
|
|
smmu->options |= ARM_SMMU_OPT_SKIP_PREFETCH;
|
|
break;
|
|
}
|
|
|
|
dev_notice(smmu->dev, "option mask 0x%x\n", smmu->options);
|
|
}
|
|
|
|
static int arm_smmu_device_acpi_probe(struct platform_device *pdev,
|
|
struct arm_smmu_device *smmu,
|
|
bool *bypass)
|
|
{
|
|
struct acpi_iort_smmu_v3 *iort_smmu;
|
|
struct device *dev = smmu->dev;
|
|
struct acpi_iort_node *node;
|
|
|
|
node = *(struct acpi_iort_node **)dev_get_platdata(dev);
|
|
|
|
/* Retrieve SMMUv3 specific data */
|
|
iort_smmu = (struct acpi_iort_smmu_v3 *)node->node_data;
|
|
|
|
acpi_smmu_get_options(iort_smmu->model, smmu);
|
|
|
|
if (iort_smmu->flags & ACPI_IORT_SMMU_V3_COHACC_OVERRIDE)
|
|
smmu->features |= ARM_SMMU_FEAT_COHERENCY;
|
|
|
|
*bypass = false;
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
static inline int arm_smmu_device_acpi_probe(struct platform_device *pdev,
|
|
struct arm_smmu_device *smmu,
|
|
bool *bypass)
|
|
{
|
|
return -ENODEV;
|
|
}
|
|
#endif
|
|
|
|
int arm_smmu_fw_probe(struct platform_device *pdev,
|
|
struct arm_smmu_device *smmu, bool *bypass)
|
|
{
|
|
if (smmu->dev->of_node)
|
|
return arm_smmu_device_dt_probe(pdev, smmu, bypass);
|
|
else
|
|
return arm_smmu_device_acpi_probe(pdev, smmu, bypass);
|
|
}
|
|
|
|
#ifdef CONFIG_ARM_SMMU_V3_SVA
|
|
bool __weak arm_smmu_sva_supported(struct arm_smmu_device *smmu)
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#define IIDR_IMPLEMENTER_ARM 0x43b
|
|
#define IIDR_PRODUCTID_ARM_MMU_600 0x483
|
|
#define IIDR_PRODUCTID_ARM_MMU_700 0x487
|
|
|
|
static void arm_smmu_device_iidr_probe(struct arm_smmu_device *smmu)
|
|
{
|
|
u32 reg;
|
|
unsigned int implementer, productid, variant, revision;
|
|
|
|
reg = readl_relaxed(smmu->base + ARM_SMMU_IIDR);
|
|
implementer = FIELD_GET(IIDR_IMPLEMENTER, reg);
|
|
productid = FIELD_GET(IIDR_PRODUCTID, reg);
|
|
variant = FIELD_GET(IIDR_VARIANT, reg);
|
|
revision = FIELD_GET(IIDR_REVISION, reg);
|
|
|
|
switch (implementer) {
|
|
case IIDR_IMPLEMENTER_ARM:
|
|
switch (productid) {
|
|
case IIDR_PRODUCTID_ARM_MMU_600:
|
|
/* Arm erratum 1076982 */
|
|
if (variant == 0 && revision <= 2)
|
|
smmu->features &= ~ARM_SMMU_FEAT_SEV;
|
|
/* Arm erratum 1209401 */
|
|
if (variant < 2)
|
|
smmu->features &= ~ARM_SMMU_FEAT_NESTING;
|
|
break;
|
|
case IIDR_PRODUCTID_ARM_MMU_700:
|
|
/* Arm erratum 2812531 */
|
|
smmu->features &= ~ARM_SMMU_FEAT_BTM;
|
|
smmu->options |= ARM_SMMU_OPT_CMDQ_FORCE_SYNC;
|
|
/* Arm errata 2268618, 2812531 */
|
|
smmu->features &= ~ARM_SMMU_FEAT_NESTING;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
|
|
{
|
|
u32 reg;
|
|
bool coherent = smmu->features & ARM_SMMU_FEAT_COHERENCY;
|
|
u8 split = smmu->strtab_cfg.split;
|
|
|
|
/* IDR0 */
|
|
reg = readl_relaxed(smmu->base + ARM_SMMU_IDR0);
|
|
|
|
/* 2-level structures */
|
|
if (FIELD_GET(IDR0_ST_LVL, reg) == IDR0_ST_LVL_2LVL)
|
|
smmu->features |= ARM_SMMU_FEAT_2_LVL_STRTAB;
|
|
|
|
if (reg & IDR0_CD2L)
|
|
smmu->features |= ARM_SMMU_FEAT_2_LVL_CDTAB;
|
|
|
|
/*
|
|
* Translation table endianness.
|
|
* We currently require the same endianness as the CPU, but this
|
|
* could be changed later by adding a new IO_PGTABLE_QUIRK.
|
|
*/
|
|
switch (FIELD_GET(IDR0_TTENDIAN, reg)) {
|
|
case IDR0_TTENDIAN_MIXED:
|
|
smmu->features |= ARM_SMMU_FEAT_TT_LE | ARM_SMMU_FEAT_TT_BE;
|
|
break;
|
|
#ifdef __BIG_ENDIAN
|
|
case IDR0_TTENDIAN_BE:
|
|
smmu->features |= ARM_SMMU_FEAT_TT_BE;
|
|
break;
|
|
#else
|
|
case IDR0_TTENDIAN_LE:
|
|
smmu->features |= ARM_SMMU_FEAT_TT_LE;
|
|
break;
|
|
#endif
|
|
default:
|
|
dev_err(smmu->dev, "unknown/unsupported TT endianness!\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Boolean feature flags */
|
|
if (IS_ENABLED(CONFIG_PCI_PRI) && reg & IDR0_PRI)
|
|
smmu->features |= ARM_SMMU_FEAT_PRI;
|
|
|
|
if (IS_ENABLED(CONFIG_PCI_ATS) && reg & IDR0_ATS)
|
|
smmu->features |= ARM_SMMU_FEAT_ATS;
|
|
|
|
if (reg & IDR0_SEV)
|
|
smmu->features |= ARM_SMMU_FEAT_SEV;
|
|
|
|
if (reg & IDR0_MSI) {
|
|
smmu->features |= ARM_SMMU_FEAT_MSI;
|
|
if (coherent)
|
|
smmu->options |= ARM_SMMU_OPT_MSIPOLL;
|
|
}
|
|
|
|
if (reg & IDR0_HYP) {
|
|
smmu->features |= ARM_SMMU_FEAT_HYP;
|
|
if (cpus_have_cap(ARM64_HAS_VIRT_HOST_EXTN))
|
|
smmu->features |= ARM_SMMU_FEAT_E2H;
|
|
}
|
|
|
|
/*
|
|
* The coherency feature as set by FW is used in preference to the ID
|
|
* register, but warn on mismatch.
|
|
*/
|
|
if (!!(reg & IDR0_COHACC) != coherent)
|
|
dev_warn(smmu->dev, "IDR0.COHACC overridden by FW configuration (%s)\n",
|
|
coherent ? "true" : "false");
|
|
|
|
switch (FIELD_GET(IDR0_STALL_MODEL, reg)) {
|
|
case IDR0_STALL_MODEL_FORCE:
|
|
smmu->features |= ARM_SMMU_FEAT_STALL_FORCE;
|
|
fallthrough;
|
|
case IDR0_STALL_MODEL_STALL:
|
|
smmu->features |= ARM_SMMU_FEAT_STALLS;
|
|
}
|
|
|
|
if (reg & IDR0_S1P)
|
|
smmu->features |= ARM_SMMU_FEAT_TRANS_S1;
|
|
|
|
if (reg & IDR0_S2P)
|
|
smmu->features |= ARM_SMMU_FEAT_TRANS_S2;
|
|
|
|
if (!(reg & (IDR0_S1P | IDR0_S2P))) {
|
|
dev_err(smmu->dev, "no translation support!\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* We only support the AArch64 table format at present */
|
|
switch (FIELD_GET(IDR0_TTF, reg)) {
|
|
case IDR0_TTF_AARCH32_64:
|
|
smmu->ias = 40;
|
|
fallthrough;
|
|
case IDR0_TTF_AARCH64:
|
|
break;
|
|
default:
|
|
dev_err(smmu->dev, "AArch64 table format not supported!\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* ASID/VMID sizes */
|
|
smmu->asid_bits = reg & IDR0_ASID16 ? 16 : 8;
|
|
smmu->vmid_bits = reg & IDR0_VMID16 ? 16 : 8;
|
|
|
|
/* IDR1 */
|
|
reg = readl_relaxed(smmu->base + ARM_SMMU_IDR1);
|
|
if (reg & (IDR1_TABLES_PRESET | IDR1_QUEUES_PRESET | IDR1_REL)) {
|
|
dev_err(smmu->dev, "embedded implementation not supported\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Queue sizes, capped to ensure natural alignment */
|
|
smmu->cmdq.q.llq.max_n_shift = min_t(u32, CMDQ_MAX_SZ_SHIFT,
|
|
FIELD_GET(IDR1_CMDQS, reg));
|
|
if (smmu->cmdq.q.llq.max_n_shift <= ilog2(CMDQ_BATCH_ENTRIES)) {
|
|
/*
|
|
* We don't support splitting up batches, so one batch of
|
|
* commands plus an extra sync needs to fit inside the command
|
|
* queue. There's also no way we can handle the weird alignment
|
|
* restrictions on the base pointer for a unit-length queue.
|
|
*/
|
|
dev_err(smmu->dev, "command queue size <= %d entries not supported\n",
|
|
CMDQ_BATCH_ENTRIES);
|
|
return -ENXIO;
|
|
}
|
|
|
|
smmu->evtq.q.llq.max_n_shift = min_t(u32, EVTQ_MAX_SZ_SHIFT,
|
|
FIELD_GET(IDR1_EVTQS, reg));
|
|
smmu->priq.q.llq.max_n_shift = min_t(u32, PRIQ_MAX_SZ_SHIFT,
|
|
FIELD_GET(IDR1_PRIQS, reg));
|
|
|
|
/* SID/SSID sizes */
|
|
smmu->ssid_bits = FIELD_GET(IDR1_SSIDSIZE, reg);
|
|
smmu->sid_bits = FIELD_GET(IDR1_SIDSIZE, reg);
|
|
smmu->iommu.max_pasids = 1UL << smmu->ssid_bits;
|
|
|
|
if ((split != 6) && (split != 8) && (split != 10)) {
|
|
dev_err(smmu->dev, "configured split not supported %d\n", split);
|
|
return -EINVAL;
|
|
}
|
|
/*
|
|
* If the SMMU supports fewer bits than would fill a single L2 stream
|
|
* table, use a linear table instead.
|
|
*/
|
|
if (smmu->sid_bits <= smmu->strtab_cfg.split)
|
|
smmu->features &= ~ARM_SMMU_FEAT_2_LVL_STRTAB;
|
|
|
|
/* IDR3 */
|
|
reg = readl_relaxed(smmu->base + ARM_SMMU_IDR3);
|
|
if (FIELD_GET(IDR3_RIL, reg))
|
|
smmu->features |= ARM_SMMU_FEAT_RANGE_INV;
|
|
|
|
/* IDR5 */
|
|
reg = readl_relaxed(smmu->base + ARM_SMMU_IDR5);
|
|
|
|
/* Maximum number of outstanding stalls */
|
|
smmu->evtq.max_stalls = FIELD_GET(IDR5_STALL_MAX, reg);
|
|
|
|
/* Page sizes */
|
|
if (reg & IDR5_GRAN64K)
|
|
smmu->pgsize_bitmap |= SZ_64K | SZ_512M;
|
|
if (reg & IDR5_GRAN16K)
|
|
smmu->pgsize_bitmap |= SZ_16K | SZ_32M;
|
|
if (reg & IDR5_GRAN4K)
|
|
smmu->pgsize_bitmap |= SZ_4K | SZ_2M | SZ_1G;
|
|
|
|
/* Input address size */
|
|
if (FIELD_GET(IDR5_VAX, reg) == IDR5_VAX_52_BIT)
|
|
smmu->features |= ARM_SMMU_FEAT_VAX;
|
|
|
|
/* Output address size */
|
|
switch (FIELD_GET(IDR5_OAS, reg)) {
|
|
case IDR5_OAS_32_BIT:
|
|
smmu->oas = 32;
|
|
break;
|
|
case IDR5_OAS_36_BIT:
|
|
smmu->oas = 36;
|
|
break;
|
|
case IDR5_OAS_40_BIT:
|
|
smmu->oas = 40;
|
|
break;
|
|
case IDR5_OAS_42_BIT:
|
|
smmu->oas = 42;
|
|
break;
|
|
case IDR5_OAS_44_BIT:
|
|
smmu->oas = 44;
|
|
break;
|
|
case IDR5_OAS_52_BIT:
|
|
smmu->oas = 52;
|
|
smmu->pgsize_bitmap |= 1ULL << 42; /* 4TB */
|
|
break;
|
|
default:
|
|
dev_info(smmu->dev,
|
|
"unknown output address size. Truncating to 48-bit\n");
|
|
fallthrough;
|
|
case IDR5_OAS_48_BIT:
|
|
smmu->oas = 48;
|
|
}
|
|
|
|
/* Set the DMA mask for our table walker */
|
|
if (dma_set_mask_and_coherent(smmu->dev, DMA_BIT_MASK(smmu->oas)))
|
|
dev_warn(smmu->dev,
|
|
"failed to set DMA mask for table walker\n");
|
|
|
|
smmu->ias = max(smmu->ias, smmu->oas);
|
|
|
|
if ((smmu->features & ARM_SMMU_FEAT_TRANS_S1) &&
|
|
(smmu->features & ARM_SMMU_FEAT_TRANS_S2))
|
|
smmu->features |= ARM_SMMU_FEAT_NESTING;
|
|
|
|
arm_smmu_device_iidr_probe(smmu);
|
|
|
|
if (arm_smmu_sva_supported(smmu))
|
|
smmu->features |= ARM_SMMU_FEAT_SVA;
|
|
|
|
dev_info(smmu->dev, "ias %lu-bit, oas %lu-bit (features 0x%08x)\n",
|
|
smmu->ias, smmu->oas, smmu->features);
|
|
return 0;
|
|
}
|
|
|
|
int arm_smmu_write_reg_sync(struct arm_smmu_device *smmu, u32 val,
|
|
unsigned int reg_off, unsigned int ack_off)
|
|
{
|
|
u32 reg;
|
|
|
|
writel_relaxed(val, smmu->base + reg_off);
|
|
return readl_relaxed_poll_timeout(smmu->base + ack_off, reg, reg == val,
|
|
1, ARM_SMMU_POLL_TIMEOUT_US);
|
|
}
|
|
|
|
/* GBPA is "special" */
|
|
int arm_smmu_update_gbpa(struct arm_smmu_device *smmu, u32 set, u32 clr)
|
|
{
|
|
int ret;
|
|
u32 reg, __iomem *gbpa = smmu->base + ARM_SMMU_GBPA;
|
|
|
|
ret = readl_relaxed_poll_timeout(gbpa, reg, !(reg & GBPA_UPDATE),
|
|
1, ARM_SMMU_POLL_TIMEOUT_US);
|
|
if (ret)
|
|
return ret;
|
|
|
|
reg &= ~clr;
|
|
reg |= set;
|
|
writel_relaxed(reg | GBPA_UPDATE, gbpa);
|
|
ret = readl_relaxed_poll_timeout(gbpa, reg, !(reg & GBPA_UPDATE),
|
|
1, ARM_SMMU_POLL_TIMEOUT_US);
|
|
|
|
if (ret)
|
|
dev_err(smmu->dev, "GBPA not responding to update\n");
|
|
return ret;
|
|
}
|
|
|
|
int arm_smmu_device_disable(struct arm_smmu_device *smmu)
|
|
{
|
|
int ret;
|
|
|
|
ret = arm_smmu_write_reg_sync(smmu, 0, ARM_SMMU_CR0, ARM_SMMU_CR0ACK);
|
|
if (ret)
|
|
dev_err(smmu->dev, "failed to clear cr0\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool arm_smmu_capable(struct device *dev, enum iommu_cap cap)
|
|
{
|
|
struct arm_smmu_master *master = dev_iommu_priv_get(dev);
|
|
|
|
switch (cap) {
|
|
case IOMMU_CAP_CACHE_COHERENCY:
|
|
/* Assume that a coherent TCU implies coherent TBUs */
|
|
return master->smmu->features & ARM_SMMU_FEAT_COHERENCY;
|
|
case IOMMU_CAP_NOEXEC:
|
|
case IOMMU_CAP_DEFERRED_FLUSH:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
struct iommu_group *arm_smmu_device_group(struct device *dev)
|
|
{
|
|
struct iommu_group *group;
|
|
|
|
/*
|
|
* We don't support devices sharing stream IDs other than PCI RID
|
|
* aliases, since the necessary ID-to-device lookup becomes rather
|
|
* impractical given a potential sparse 32-bit stream ID space.
|
|
*/
|
|
if (dev_is_pci(dev))
|
|
group = pci_device_group(dev);
|
|
else
|
|
group = generic_device_group(dev);
|
|
|
|
return group;
|
|
}
|
|
|
|
int arm_smmu_of_xlate(struct device *dev, struct of_phandle_args *args)
|
|
{
|
|
return iommu_fwspec_add_ids(dev, args->args, 1);
|
|
}
|
|
|
|
void arm_smmu_get_resv_regions(struct device *dev, struct list_head *head)
|
|
{
|
|
struct iommu_resv_region *region;
|
|
int prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO;
|
|
|
|
region = iommu_alloc_resv_region(MSI_IOVA_BASE, MSI_IOVA_LENGTH,
|
|
prot, IOMMU_RESV_SW_MSI, GFP_KERNEL);
|
|
if (!region)
|
|
return;
|
|
|
|
list_add_tail(®ion->list, head);
|
|
|
|
iommu_dma_get_resv_regions(dev, head);
|
|
}
|
|
|
|
int arm_smmu_init_one_queue(struct arm_smmu_device *smmu,
|
|
struct arm_smmu_queue *q,
|
|
void __iomem *page,
|
|
unsigned long prod_off,
|
|
unsigned long cons_off,
|
|
size_t dwords, const char *name)
|
|
{
|
|
size_t qsz;
|
|
|
|
do {
|
|
qsz = ((1 << q->llq.max_n_shift) * dwords) << 3;
|
|
q->base = dmam_alloc_coherent(smmu->dev, qsz, &q->base_dma,
|
|
GFP_KERNEL);
|
|
if (q->base || qsz < PAGE_SIZE)
|
|
break;
|
|
|
|
q->llq.max_n_shift--;
|
|
} while (1);
|
|
|
|
if (!q->base) {
|
|
dev_err(smmu->dev,
|
|
"failed to allocate queue (0x%zx bytes) for %s\n",
|
|
qsz, name);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (!WARN_ON(q->base_dma & (qsz - 1))) {
|
|
dev_info(smmu->dev, "allocated %u entries for %s\n",
|
|
1 << q->llq.max_n_shift, name);
|
|
}
|
|
|
|
q->prod_reg = page + prod_off;
|
|
q->cons_reg = page + cons_off;
|
|
q->ent_dwords = dwords;
|
|
|
|
q->q_base = Q_BASE_RWA;
|
|
q->q_base |= q->base_dma & Q_BASE_ADDR_MASK;
|
|
q->q_base |= FIELD_PREP(Q_BASE_LOG2SIZE, q->llq.max_n_shift);
|
|
|
|
q->llq.prod = q->llq.cons = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Stream table initialization functions */
|
|
void arm_smmu_write_strtab_l1_desc(__le64 *dst,
|
|
struct arm_smmu_strtab_l1_desc *desc)
|
|
{
|
|
u64 val = 0;
|
|
|
|
val |= FIELD_PREP(STRTAB_L1_DESC_SPAN, desc->span);
|
|
val |= desc->l2ptr_dma & STRTAB_L1_DESC_L2PTR_MASK;
|
|
|
|
/* See comment in arm_smmu_write_ctx_desc() */
|
|
WRITE_ONCE(*dst, cpu_to_le64(val));
|
|
}
|
|
|
|
|
|
static int arm_smmu_init_l1_strtab(struct arm_smmu_device *smmu)
|
|
{
|
|
unsigned int i;
|
|
struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
|
|
void *strtab = smmu->strtab_cfg.strtab;
|
|
|
|
cfg->l1_desc = devm_kcalloc(smmu->dev, cfg->num_l1_ents,
|
|
sizeof(*cfg->l1_desc), GFP_KERNEL);
|
|
if (!cfg->l1_desc)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < cfg->num_l1_ents; ++i) {
|
|
arm_smmu_write_strtab_l1_desc(strtab, &cfg->l1_desc[i]);
|
|
strtab += STRTAB_L1_DESC_DWORDS << 3;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu)
|
|
{
|
|
void *strtab;
|
|
u64 reg;
|
|
u32 size, l1size;
|
|
struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
|
|
|
|
/* Calculate the L1 size, capped to the SIDSIZE. */
|
|
size = STRTAB_L1_SZ_SHIFT - (ilog2(STRTAB_L1_DESC_DWORDS) + 3);
|
|
size = min(size, smmu->sid_bits - smmu->strtab_cfg.split);
|
|
cfg->num_l1_ents = 1 << size;
|
|
|
|
size += smmu->strtab_cfg.split;
|
|
if (size < smmu->sid_bits)
|
|
dev_warn(smmu->dev,
|
|
"2-level strtab only covers %u/%u bits of SID\n",
|
|
size, smmu->sid_bits);
|
|
|
|
l1size = cfg->num_l1_ents * (STRTAB_L1_DESC_DWORDS << 3);
|
|
strtab = dmam_alloc_coherent(smmu->dev, l1size, &cfg->strtab_dma,
|
|
GFP_KERNEL);
|
|
if (!strtab) {
|
|
dev_err(smmu->dev,
|
|
"failed to allocate l1 stream table (%u bytes)\n",
|
|
l1size);
|
|
return -ENOMEM;
|
|
}
|
|
cfg->strtab = strtab;
|
|
|
|
/* Configure strtab_base_cfg for 2 levels */
|
|
reg = FIELD_PREP(STRTAB_BASE_CFG_FMT, STRTAB_BASE_CFG_FMT_2LVL);
|
|
reg |= FIELD_PREP(STRTAB_BASE_CFG_LOG2SIZE, size);
|
|
reg |= FIELD_PREP(STRTAB_BASE_CFG_SPLIT, smmu->strtab_cfg.split);
|
|
cfg->strtab_base_cfg = reg;
|
|
|
|
return arm_smmu_init_l1_strtab(smmu);
|
|
}
|
|
|
|
static int arm_smmu_init_strtab_linear(struct arm_smmu_device *smmu)
|
|
{
|
|
void *strtab;
|
|
u64 reg;
|
|
u32 size;
|
|
struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
|
|
|
|
size = (1 << smmu->sid_bits) * (STRTAB_STE_DWORDS << 3);
|
|
strtab = dmam_alloc_coherent(smmu->dev, size, &cfg->strtab_dma,
|
|
GFP_KERNEL);
|
|
if (!strtab) {
|
|
dev_err(smmu->dev,
|
|
"failed to allocate linear stream table (%u bytes)\n",
|
|
size);
|
|
return -ENOMEM;
|
|
}
|
|
cfg->strtab = strtab;
|
|
cfg->num_l1_ents = 1 << smmu->sid_bits;
|
|
|
|
/* Configure strtab_base_cfg for a linear table covering all SIDs */
|
|
reg = FIELD_PREP(STRTAB_BASE_CFG_FMT, STRTAB_BASE_CFG_FMT_LINEAR);
|
|
reg |= FIELD_PREP(STRTAB_BASE_CFG_LOG2SIZE, smmu->sid_bits);
|
|
cfg->strtab_base_cfg = reg;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int arm_smmu_init_strtab(struct arm_smmu_device *smmu)
|
|
{
|
|
u64 reg;
|
|
int ret;
|
|
|
|
if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)
|
|
ret = arm_smmu_init_strtab_2lvl(smmu);
|
|
else
|
|
ret = arm_smmu_init_strtab_linear(smmu);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Set the strtab base address */
|
|
reg = smmu->strtab_cfg.strtab_dma & STRTAB_BASE_ADDR_MASK;
|
|
reg |= STRTAB_BASE_RA;
|
|
smmu->strtab_cfg.strtab_base = reg;
|
|
|
|
ida_init(&smmu->vmid_map);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void arm_smmu_free_msis(void *data)
|
|
{
|
|
struct device *dev = data;
|
|
platform_msi_domain_free_irqs(dev);
|
|
}
|
|
|
|
static void arm_smmu_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
|
|
{
|
|
phys_addr_t doorbell;
|
|
struct device *dev = msi_desc_to_dev(desc);
|
|
struct arm_smmu_device *smmu = dev_get_drvdata(dev);
|
|
phys_addr_t *cfg = arm_smmu_msi_cfg[desc->msi_index];
|
|
|
|
doorbell = (((u64)msg->address_hi) << 32) | msg->address_lo;
|
|
doorbell &= MSI_CFG0_ADDR_MASK;
|
|
|
|
writeq_relaxed(doorbell, smmu->base + cfg[0]);
|
|
writel_relaxed(msg->data, smmu->base + cfg[1]);
|
|
writel_relaxed(ARM_SMMU_MEMATTR_DEVICE_nGnRE, smmu->base + cfg[2]);
|
|
}
|
|
|
|
static void arm_smmu_setup_msis(struct arm_smmu_device *smmu)
|
|
{
|
|
int ret, nvec = ARM_SMMU_MAX_MSIS;
|
|
struct device *dev = smmu->dev;
|
|
|
|
/* Clear the MSI address regs */
|
|
writeq_relaxed(0, smmu->base + ARM_SMMU_GERROR_IRQ_CFG0);
|
|
writeq_relaxed(0, smmu->base + ARM_SMMU_EVTQ_IRQ_CFG0);
|
|
|
|
if (smmu->features & ARM_SMMU_FEAT_PRI)
|
|
writeq_relaxed(0, smmu->base + ARM_SMMU_PRIQ_IRQ_CFG0);
|
|
else
|
|
nvec--;
|
|
|
|
if (!(smmu->features & ARM_SMMU_FEAT_MSI))
|
|
return;
|
|
|
|
if (!dev->msi.domain) {
|
|
dev_info(smmu->dev, "msi_domain absent - falling back to wired irqs\n");
|
|
return;
|
|
}
|
|
|
|
/* Allocate MSIs for evtq, gerror and priq. Ignore cmdq */
|
|
ret = platform_msi_domain_alloc_irqs(dev, nvec, arm_smmu_write_msi_msg);
|
|
if (ret) {
|
|
dev_warn(dev, "failed to allocate MSIs - falling back to wired irqs\n");
|
|
return;
|
|
}
|
|
|
|
smmu->evtq.q.irq = msi_get_virq(dev, EVTQ_MSI_INDEX);
|
|
smmu->gerr_irq = msi_get_virq(dev, GERROR_MSI_INDEX);
|
|
smmu->priq.q.irq = msi_get_virq(dev, PRIQ_MSI_INDEX);
|
|
|
|
/* Add callback to free MSIs on teardown */
|
|
devm_add_action(dev, arm_smmu_free_msis, dev);
|
|
}
|
|
|
|
void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu,
|
|
irqreturn_t evtqirq(int irq, void *dev),
|
|
irqreturn_t gerrorirq(int irq, void *dev),
|
|
irqreturn_t priirq(int irq, void *dev))
|
|
{
|
|
int irq, ret;
|
|
|
|
arm_smmu_setup_msis(smmu);
|
|
|
|
/* Request interrupt lines */
|
|
irq = smmu->evtq.q.irq;
|
|
if (irq) {
|
|
ret = devm_request_threaded_irq(smmu->dev, irq, NULL,
|
|
evtqirq,
|
|
IRQF_ONESHOT,
|
|
"arm-smmu-v3-evtq", smmu);
|
|
if (ret < 0)
|
|
dev_warn(smmu->dev, "failed to enable evtq irq\n");
|
|
} else {
|
|
dev_warn(smmu->dev, "no evtq irq - events will not be reported!\n");
|
|
}
|
|
|
|
irq = smmu->gerr_irq;
|
|
if (irq) {
|
|
ret = devm_request_irq(smmu->dev, irq, gerrorirq,
|
|
0, "arm-smmu-v3-gerror", smmu);
|
|
if (ret < 0)
|
|
dev_warn(smmu->dev, "failed to enable gerror irq\n");
|
|
} else {
|
|
dev_warn(smmu->dev, "no gerr irq - errors will not be reported!\n");
|
|
}
|
|
|
|
if (smmu->features & ARM_SMMU_FEAT_PRI) {
|
|
irq = smmu->priq.q.irq;
|
|
if (irq) {
|
|
ret = devm_request_threaded_irq(smmu->dev, irq, NULL,
|
|
priirq,
|
|
IRQF_ONESHOT,
|
|
"arm-smmu-v3-priq",
|
|
smmu);
|
|
if (ret < 0)
|
|
dev_warn(smmu->dev,
|
|
"failed to enable priq irq\n");
|
|
} else {
|
|
dev_warn(smmu->dev, "no priq irq - PRI will be broken\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
void arm_smmu_probe_irq(struct platform_device *pdev,
|
|
struct arm_smmu_device *smmu)
|
|
{
|
|
int irq;
|
|
|
|
irq = platform_get_irq_byname_optional(pdev, "combined");
|
|
if (irq > 0)
|
|
smmu->combined_irq = irq;
|
|
else {
|
|
irq = platform_get_irq_byname_optional(pdev, "eventq");
|
|
if (irq > 0)
|
|
smmu->evtq.q.irq = irq;
|
|
|
|
irq = platform_get_irq_byname_optional(pdev, "priq");
|
|
if (irq > 0)
|
|
smmu->priq.q.irq = irq;
|
|
|
|
irq = platform_get_irq_byname_optional(pdev, "gerror");
|
|
if (irq > 0)
|
|
smmu->gerr_irq = irq;
|
|
}
|
|
}
|
|
|
|
int arm_smmu_register_iommu(struct arm_smmu_device *smmu,
|
|
struct iommu_ops *ops, phys_addr_t ioaddr)
|
|
{
|
|
int ret;
|
|
struct device *dev = smmu->dev;
|
|
|
|
ret = iommu_device_sysfs_add(&smmu->iommu, dev, NULL,
|
|
"smmu3.%pa", &ioaddr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = iommu_device_register(&smmu->iommu, ops, dev);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to register iommu\n");
|
|
iommu_device_sysfs_remove(&smmu->iommu);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void arm_smmu_unregister_iommu(struct arm_smmu_device *smmu)
|
|
{
|
|
iommu_device_unregister(&smmu->iommu);
|
|
iommu_device_sysfs_remove(&smmu->iommu);
|
|
}
|