mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-07 03:15:31 +09:00
ANDROID: KVM: arm64: Implement IRQ handler for S2MPU faults
The S2MPU can be configured to trigger an interrupt on faults: access permission (both regular and during page table walks) and if no matching context ID is found for request's VID (v9 only). When interrupt information is provided in the S2MPU's DT node, parse the information and enable an IRQ handler. Later patch will enable the functionality in the S2MPU. Test: builds, boots Bug: 190463801 Signed-off-by: David Brazdil <dbrazdil@google.com> Change-Id: I5e26a64fc2f09f96f36a93ea9bc5bf3035a71077
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* Author: David Brazdil <dbrazdil@google.com>
|
||||
*/
|
||||
|
||||
#include <linux/io-64-nonatomic-hi-lo.h>
|
||||
#include <linux/kvm_host.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user