diff --git a/arch/arm64/include/asm/kvm_s2mpu.h b/arch/arm64/include/asm/kvm_s2mpu.h index 1c4f4a85cd53..d513c56b2c9c 100644 --- a/arch/arm64/include/asm/kvm_s2mpu.h +++ b/arch/arm64/include/asm/kvm_s2mpu.h @@ -14,8 +14,16 @@ #define ALL_VIDS_BITMAP GENMASK(NR_VIDS - 1, 0) +#define REG_NS_INTERRUPT_CLEAR 0x2c #define REG_NS_VERSION 0x60 #define REG_NS_NUM_CONTEXT 0x100 +#define REG_NS_FAULT_STATUS 0x2000 +#define REG_NS_FAULT_PA_LOW(vid) (0x2004 + ((vid) * 0x20)) +#define REG_NS_FAULT_PA_HIGH(vid) (0x2008 + ((vid) * 0x20)) +#define REG_NS_FAULT_INFO(vid) (0x2010 + ((vid) * 0x20)) + +/* For use with hi_lo_readq_relaxed(). */ +#define REG_NS_FAULT_PA_HIGH_LOW(vid) REG_NS_FAULT_PA_LOW(vid) #define VERSION_MAJOR_ARCH_VER_MASK GENMASK(31, 28) #define VERSION_MINOR_ARCH_VER_MASK GENMASK(27, 24) @@ -33,6 +41,16 @@ #define CONTEXT_CFG_VALID_VID_CTX_VID(ctx, vid) \ FIELD_PREP(GENMASK((4 * (ctx) + 2), 4 * (ctx)), (vid)) +#define NR_FAULT_INFO_REGS 8 +#define FAULT_INFO_VID_MASK GENMASK(26, 24) +#define FAULT_INFO_TYPE_MASK GENMASK(23, 21) +#define FAULT_INFO_TYPE_CONTEXT 0x4 /* v9 only */ +#define FAULT_INFO_TYPE_AP 0x2 +#define FAULT_INFO_TYPE_MPTW 0x1 +#define FAULT_INFO_RW_BIT BIT(20) +#define FAULT_INFO_LEN_MASK GENMASK(19, 16) +#define FAULT_INFO_ID_MASK GENMASK(15, 0) + enum s2mpu_version { S2MPU_VERSION_8 = 0x11000000, S2MPU_VERSION_9 = 0x20000000, diff --git a/arch/arm64/kvm/iommu/s2mpu.c b/arch/arm64/kvm/iommu/s2mpu.c index 9bbd473e276c..5ac5544a30c9 100644 --- a/arch/arm64/kvm/iommu/s2mpu.c +++ b/arch/arm64/kvm/iommu/s2mpu.c @@ -4,6 +4,7 @@ * Author: David Brazdil */ +#include #include #include @@ -16,6 +17,62 @@ (CONTEXT_CFG_VALID_VID_CTX_VID(ctxid, vid) \ | (((ctxid) < (nr_ctx)) ? CONTEXT_CFG_VALID_VID_CTX_VALID(ctxid) : 0)) +struct s2mpu_irq_info { + struct device *dev; + void __iomem *va; +}; + +static irqreturn_t s2mpu_irq_handler(int irq, void *data) +{ + struct s2mpu_irq_info *info = data; + unsigned int vid; + u32 vid_bmap, fault_info; + phys_addr_t fault_pa; + const char *fault_type; + irqreturn_t ret = IRQ_NONE; + + while ((vid_bmap = readl_relaxed(info->va + REG_NS_FAULT_STATUS))) { + WARN_ON_ONCE(vid_bmap & (~ALL_VIDS_BITMAP)); + vid = __ffs(vid_bmap); + + fault_pa = hi_lo_readq_relaxed(info->va + REG_NS_FAULT_PA_HIGH_LOW(vid)); + fault_info = readl_relaxed(info->va + REG_NS_FAULT_INFO(vid)); + WARN_ON(FIELD_GET(FAULT_INFO_VID_MASK, fault_info) != vid); + + switch (FIELD_GET(FAULT_INFO_TYPE_MASK, fault_info)) { + case FAULT_INFO_TYPE_MPTW: + fault_type = "MPTW fault"; + break; + case FAULT_INFO_TYPE_AP: + fault_type = "access permission fault"; + break; + case FAULT_INFO_TYPE_CONTEXT: + fault_type = "context fault"; + break; + default: + fault_type = "unknown fault"; + break; + } + + dev_err(info->dev, "\n" + "============== S2MPU FAULT DETECTED ==============\n" + " PA=0x%pap, FAULT_INFO=0x%08x\n" + " DIRECTION: %s, TYPE: %s\n" + " VID=%u, REQ_LENGTH=%lu, REQ_AXI_ID=%lu\n" + "==================================================\n", + &fault_pa, fault_info, + (fault_info & FAULT_INFO_RW_BIT) ? "write" : "read", + fault_type, vid, + FIELD_GET(FAULT_INFO_LEN_MASK, fault_info), + FIELD_GET(FAULT_INFO_ID_MASK, fault_info)); + + writel_relaxed(BIT(vid), info->va + REG_NS_INTERRUPT_CLEAR); + ret = IRQ_HANDLED; + } + + return ret; +} + static u32 gen_ctx_cfg_valid_vid(struct platform_device *pdev, unsigned int num_ctx, u32 vid_bmap) { @@ -68,6 +125,44 @@ static int s2mpu_probe_v9(struct platform_device *pdev, void __iomem *kaddr) return 0; } +/** + * Parse interrupt information from DT and if found, register IRQ handler. + * This is considered optional and will not fail even if the initialization is + * unsuccessful. In that case the IRQ will remain masked. + */ +static void s2mpu_probe_irq(struct platform_device *pdev, void __iomem *kaddr) +{ + struct s2mpu_irq_info *irq_info; + int ret, irq; + + irq = platform_get_irq_optional(pdev, 0); + + if (irq == -ENXIO) + return; /* No IRQ specified. */ + + if (irq < 0) { + /* IRQ specified but failed to parse. */ + dev_err(&pdev->dev, "failed to parse IRQ, IRQ not enabled"); + return; + } + + irq_info = devm_kmalloc(&pdev->dev, sizeof(*irq_info), GFP_KERNEL); + if (!irq_info) + return; + + *irq_info = (struct s2mpu_irq_info){ + .dev = &pdev->dev, + .va = kaddr, + }; + + ret = devm_request_irq(&pdev->dev, irq, s2mpu_irq_handler, 0, + dev_name(&pdev->dev), irq_info); + if (ret) { + dev_err(&pdev->dev, "failed to register IRQ, IRQ not enabled"); + return; + } +} + static int s2mpu_probe(struct platform_device *pdev) { struct resource *res; @@ -114,6 +209,13 @@ static int s2mpu_probe(struct platform_device *pdev) return ret; } + /* + * Try to parse IRQ information. This is optional as it only affects + * runtime fault reporting, and therefore errors do not fail the whole + * driver initialization. + */ + s2mpu_probe_irq(pdev, kaddr); + version = readl_relaxed(kaddr + REG_NS_VERSION); switch (version & VERSION_CHECK_MASK) { case S2MPU_VERSION_8: