From: Lokesh Vutla <[email protected]>

A System Memory Management Unit(SMMU) performs a task analogous to a
CPU's MMU, translating addresses for device requests from system I/O
devices before the requests are passed into the system interconnect.

Implement a driver for SMMU v3 that maps and unmaps memory for specified
stream ids. This driver supports only stage 2 translation and right now
inmates cannot enable stage 1 translations. Because of this limitation,
only assignment of device to a guest VM is supported. For isolation and
scatter-gather features stage-1 translation should be clubbed along
with the existing driver.

This driver is implemented based on the following assumptions:
- Running on a Little endian 64 bit core compatible with ARM v8 architecture
- SMMU supporting only AARCH64 mode
- SMMU AARCH 64 stage 2 translation configurations are compatible with
  ARMv8 VMSA. So re using the translation tables of CPU for SMMU.

Inorder to enable stage 1 translations by cells, jailhouse should implement
a emulated smmu driver that traps all smmu ste/translation updates. Only
these updates are trapped jailhouse should do policing on the updates
and make the updates in the proper locations.

This driver is loosely based on the Linux kernel SMMU v3 driver.

Signed-off-by: Lokesh Vutla <[email protected]>
---
 hypervisor/arch/arm64/Kbuild         |    2 +-
 hypervisor/arch/arm64/smmu-v3.c      | 1017 ++++++++++++++++++++++++++
 hypervisor/include/jailhouse/entry.h |    1 +
 3 files changed, 1019 insertions(+), 1 deletion(-)
 create mode 100644 hypervisor/arch/arm64/smmu-v3.c

diff --git a/hypervisor/arch/arm64/Kbuild b/hypervisor/arch/arm64/Kbuild
index 7283a008..323b78b6 100644
--- a/hypervisor/arch/arm64/Kbuild
+++ b/hypervisor/arch/arm64/Kbuild
@@ -20,4 +20,4 @@ always := lib.a
 # irqchip (common-objs-y), <generic units>
 
 lib-y := $(common-objs-y)
-lib-y += entry.o setup.o control.o mmio.o paging.o caches.o traps.o
+lib-y += entry.o setup.o control.o mmio.o paging.o caches.o traps.o smmu-v3.o
diff --git a/hypervisor/arch/arm64/smmu-v3.c b/hypervisor/arch/arm64/smmu-v3.c
new file mode 100644
index 00000000..09f96b37
--- /dev/null
+++ b/hypervisor/arch/arm64/smmu-v3.c
@@ -0,0 +1,1017 @@
+/*
+ * Jailhouse AArch64 support
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+ *
+ * Authors:
+ *  Lokesh Vutla <[email protected]>
+ *
+ * Driver supports only stage 2 translation and right now inmates cannot
+ * enable stage 1 translations. Because of this current limitation only
+ * assignment of device to a guest VM is supported. For isolation and
+ * scatter-gather features stage-1 translation should be clubbed along
+ * with the existing driver.
+ *
+ * This driver is implemented based on the following assumptions:
+ *  - Running on a Little endian 64 bit core compatible with ARM v8 
architecture
+ *  - SMMU supporting only AARCH64 mode
+ *  - SMMU AARCH 64 stage 2 translation configurations are compatible with
+ *    ARMv8 VMSA. So re using the translation tables of CPU for SMMU.
+ *
+ * This driver is loosely based on the Linux kernel SMMU v3 driver.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include <jailhouse/control.h>
+#include <jailhouse/paging.h>
+#include <jailhouse/printk.h>
+#include <jailhouse/string.h>
+#include <asm/control.h>
+#include <jailhouse/unit.h>
+#include <asm/iommu.h>
+#include <jailhouse/cell.h>
+
+/* MMIO registers */
+#define ARM_SMMU_IDR0                  0x0
+#define IDR0_ST_LVL                    BIT_MASK(28, 27)
+#define IDR0_ST_LVL_2LVL               1
+#define IDR0_S2P                       (1 << 0)
+
+#define ARM_SMMU_IDR1                  0x4
+#define IDR1_TABLES_PRESET             (1 << 30)
+#define IDR1_QUEUES_PRESET             (1 << 29)
+#define IDR1_REL                       (1 << 28)
+#define IDR1_CMDQS                     BIT_MASK(25, 21)
+#define IDR1_EVTQS                     BIT_MASK(20, 16)
+#define IDR1_SSIDSIZE                  BIT_MASK(10, 6)
+#define IDR1_SIDSIZE                   BIT_MASK(5, 0)
+
+#define ARM_SMMU_CR0                   0x20
+#define CR0_CMDQEN                     (1 << 3)
+#define CR0_EVTQEN                     (1 << 2)
+#define CR0_SMMUEN                     (1 << 0)
+
+#define ARM_SMMU_CR0ACK                        0x24
+
+#define ARM_SMMU_CR1                   0x28
+#define CR1_TABLE_SH                   BIT_MASK(11, 10)
+#define CR1_TABLE_OC                   BIT_MASK(9, 8)
+#define CR1_TABLE_IC                   BIT_MASK(7, 6)
+#define CR1_QUEUE_SH                   BIT_MASK(5, 4)
+#define CR1_QUEUE_OC                   BIT_MASK(3, 2)
+#define CR1_QUEUE_IC                   BIT_MASK(1, 0)
+/* CR1 cacheability fields don't quite follow the usual TCR-style encoding */
+#define CR1_CACHE_NC                   0
+#define CR1_CACHE_WB                   1
+#define CR1_CACHE_WT                   2
+
+#define ARM_SMMU_CR2                   0x2c
+#define CR2_PTM                                (1 << 2)
+#define CR2_RECINVSID                  (1 << 1)
+#define CR2_E2H                                (1 << 0)
+
+#define ARM_SMMU_STRTAB_BASE           0x80
+#define STRTAB_BASE_RA                 (1UL << 62)
+#define STRTAB_BASE_ADDR_MASK          BIT_MASK(51, 6)
+
+#define ARM_SMMU_STRTAB_BASE_CFG       0x88
+#define STRTAB_BASE_CFG_FMT            BIT_MASK(17, 16)
+#define STRTAB_BASE_CFG_FMT_LINEAR     0
+#define STRTAB_BASE_CFG_FMT_2LVL       1
+#define STRTAB_BASE_CFG_SPLIT          BIT_MASK(10, 6)
+#define STRTAB_BASE_CFG_LOG2SIZE       BIT_MASK(5, 0)
+
+#define ARM_SMMU_CMDQ_BASE             0x90
+#define ARM_SMMU_CMDQ_PROD             0x98
+#define ARM_SMMU_CMDQ_CONS             0x9c
+
+#define ARM_SMMU_EVTQ_BASE             0xa0
+#define ARM_SMMU_EVTQ_PROD             0x100a8
+#define ARM_SMMU_EVTQ_CONS             0x100ac
+#define ARM_SMMU_EVTQ_IRQ_CFG0         0xb0
+#define ARM_SMMU_EVTQ_IRQ_CFG1         0xb8
+#define ARM_SMMU_EVTQ_IRQ_CFG2         0xbc
+
+/* Common memory attribute values */
+#define ARM_SMMU_SH_NSH                        0
+#define ARM_SMMU_SH_OSH                        2
+#define ARM_SMMU_SH_ISH                        3
+#define ARM_SMMU_MEMATTR_DEVICE_nGnRE  0x1
+#define ARM_SMMU_MEMATTR_OIWB          0xf
+
+#define Q_IDX(q, p)                    ((p) & ((1 << (q)->max_n_shift) - 1))
+#define Q_WRP(q, p)                    ((p) & (1 << (q)->max_n_shift))
+#define Q_OVERFLOW_FLAG                        (1 << 31)
+#define Q_OVF(q, p)                    ((p) & Q_OVERFLOW_FLAG)
+#define Q_ENT(q, p)                    ((q)->base +                    \
+                                        Q_IDX(q, p) * (q)->ent_dwords)
+
+#define Q_BASE_RWA                     (1UL << 62)
+#define Q_BASE_ADDR_MASK               BIT_MASK(51, 5)
+#define Q_BASE_LOG2SIZE                        BIT_MASK(4, 0)
+
+/*
+ * Stream table.
+ *
+ * Linear: Enough to cover 1 << IDR1.SIDSIZE entries
+ * 2lvl: 128k L1 entries,
+ *       256 lazy entries per table (each table covers a PCI bus)
+ */
+#define STRTAB_L1_SZ_SHIFT             20
+#define STRTAB_SPLIT                   8
+
+#define STRTAB_L1_DESC_DWORDS          1
+#define STRTAB_L1_DESC_SPAN            BIT_MASK(4, 0)
+#define STRTAB_L1_DESC_L2PTR_MASK      BIT_MASK(51, 6)
+
+#define STRTAB_STE_DWORDS              8
+#define STRTAB_STE_DWORDS_BITS         3
+#define STRTAB_STE_0_V                 (1UL << 0)
+#define STRTAB_STE_0_CFG               BIT_MASK(3, 1)
+#define STRTAB_STE_0_CFG_ABORT         0
+#define STRTAB_STE_0_CFG_BYPASS                4
+#define STRTAB_STE_0_CFG_S1_TRANS      5
+#define STRTAB_STE_0_CFG_S2_TRANS      6
+
+#define STRTAB_STE_1_SHCFG             BIT_MASK(45, 44)
+#define STRTAB_STE_1_SHCFG_INCOMING    1UL
+
+#define STRTAB_STE_2_S2VMID            BIT_MASK(15, 0)
+#define STRTAB_STE_2_VTCR              BIT_MASK(50, 32)
+#define STRTAB_STE_2_S2AA64            (1UL << 51)
+#define STRTAB_STE_2_S2ENDI            (1UL << 52)
+#define STRTAB_STE_2_S2PTW             (1UL << 54)
+#define STRTAB_STE_2_S2R               (1UL << 58)
+
+#define STRTAB_STE_3_S2TTB_MASK                BIT_MASK(51, 4)
+
+/* Command queue */
+#define CMDQ_ENT_DWORDS                        2
+#define CMDQ_MAX_SZ_SHIFT              8
+
+#define CMDQ_CONS_ERR                  BIT_MASK(30, 24)
+#define CMDQ_ERR_CERROR_NONE_IDX       0
+#define CMDQ_ERR_CERROR_ILL_IDX                1
+#define CMDQ_ERR_CERROR_ABT_IDX                2
+
+#define CMDQ_0_OP                      BIT_MASK(7, 0)
+#define CMDQ_0_SSV                     (1UL << 11)
+
+#define CMDQ_PREFETCH_0_SID            BIT_MASK(63, 32)
+#define CMDQ_PREFETCH_1_SIZE           BIT_MASK(4, 0)
+#define CMDQ_PREFETCH_1_ADDR_MASK      BIT_MASK(63, 12)
+
+#define CMDQ_CFGI_0_SID                        BIT_MASK(63, 32)
+#define CMDQ_CFGI_1_LEAF               (1UL << 0)
+#define CMDQ_CFGI_1_RANGE              BIT_MASK(4, 0)
+
+#define CMDQ_TLBI_0_VMID               BIT_MASK(47, 32)
+#define CMDQ_TLBI_0_ASID               BIT_MASK(63, 48)
+#define CMDQ_TLBI_1_LEAF               (1UL << 0)
+#define CMDQ_TLBI_1_VA_MASK            BIT_MASK(63, 12)
+#define CMDQ_TLBI_1_IPA_MASK           BIT_MASK(51, 12)
+
+#define CMDQ_PRI_0_SSID                        BIT_MASK(31, 12)
+#define CMDQ_PRI_0_SID                 BIT_MASK(63, 32)
+#define CMDQ_PRI_1_GRPID               BIT_MASK(8, 0)
+#define CMDQ_PRI_1_RESP                        BIT_MASK(13, 12)
+
+#define CMDQ_SYNC_0_CS                 BIT_MASK(13, 12)
+#define CMDQ_SYNC_0_CS_NONE            0
+#define CMDQ_SYNC_0_CS_IRQ             1
+#define CMDQ_SYNC_0_CS_SEV             2
+#define CMDQ_SYNC_0_MSH                        BIT_MASK(23, 22)
+#define CMDQ_SYNC_0_MSIATTR            BIT_MASK(27, 24)
+#define CMDQ_SYNC_0_MSIDATA            BIT_MASK(63, 32)
+#define CMDQ_SYNC_1_MSIADDR_MASK       BIT_MASK(51, 2)
+
+/* Event queue */
+#define EVTQ_ENT_DWORDS                        4
+#define EVTQ_MAX_SZ_SHIFT              7
+
+#define EVTQ_0_ID                      BIT_MASK(7, 0)
+
+#define ARM_SMMU_SYNC_TIMEOUT          1000000
+
+#define FIELD_PREP(mask, val)  \
+                       (((u64)(val) << (__builtin_ffsl((mask)) - 1)) & (mask))
+#define FIELD_GET(mask, reg)   \
+                       (((reg) & (mask)) >> (__builtin_ffsl((mask)) - 1))
+
+#define CMDQ_OP_PREFETCH_CFG   0x1
+#define CMDQ_OP_CFGI_STE       0x3
+#define CMDQ_OP_CFGI_ALL       0x4
+#define CMDQ_OP_TLBI_NH_ASID   0x11
+#define CMDQ_OP_TLBI_NH_VA     0x12
+#define CMDQ_OP_TLBI_EL2_ALL   0x20
+#define CMDQ_OP_TLBI_S12_VMALL 0x28
+#define CMDQ_OP_TLBI_S2_IPA    0x2a
+#define CMDQ_OP_TLBI_NSNH_ALL  0x30
+#define CMDQ_OP_CMD_SYNC       0x46
+#define ARM_SMMU_FEAT_2_LVL_STRTAB     (1 << 0)
+
+/* High-level queue structures */
+struct arm_smmu_cmdq_ent {
+       /* Common fields */
+       u8                              opcode;
+       bool                            substream_valid;
+
+       /* Command-specific fields */
+       union {
+               struct {
+                       u32                     sid;
+                       union {
+                               bool            leaf;
+                               u8              span;
+                       };
+               } cfgi;
+
+               struct {
+                       u16                     asid;
+                       u16                     vmid;
+                       bool                    leaf;
+                       u64                     addr;
+               } tlbi;
+
+               struct {
+                       u32                     msidata;
+                       u64                     msiaddr;
+               } sync;
+       };
+};
+
+struct arm_smmu_queue {
+       u64     *base;
+       u64     base_dma;
+       u64     q_base;
+       u64     ent_dwords;
+       u32     max_n_shift;
+       u32     prod;
+       u32     cons;
+       u32     *prod_reg;
+       u32     *cons_reg;
+};
+
+struct arm_smmu_cmdq {
+       struct arm_smmu_queue           q;
+       spinlock_t                      lock;
+};
+
+struct arm_smmu_evtq {
+       struct arm_smmu_queue           q;
+};
+
+/* High-level stream table structures */
+struct arm_smmu_strtab_l1_desc {
+       u8      span;
+       __u64   *l2ptr;
+       u64     l2ptr_dma;
+       u32     active_stes;
+};
+
+struct arm_smmu_strtab_cfg {
+       __u64                           *strtab;
+       u64                             strtab_dma;
+       struct arm_smmu_strtab_l1_desc  *l1_desc;
+       unsigned int                    num_l1_ents;
+       u64                             strtab_base;
+       u32                             strtab_base_cfg;
+};
+
+/* An SMMUv3 instance */
+struct arm_smmu_device {
+       void                            *base;
+       u32                             features;
+       struct arm_smmu_cmdq            cmdq;
+       struct arm_smmu_evtq            evtq;
+       unsigned int                    sid_bits;
+       struct arm_smmu_strtab_cfg      strtab_cfg;
+} smmu[JAILHOUSE_MAX_IOMMU_UNITS];
+
+/* Low-level queue manipulation functions */
+static bool queue_full(struct arm_smmu_queue *q)
+{
+       return Q_IDX(q, q->prod) == Q_IDX(q, q->cons) &&
+              Q_WRP(q, q->prod) != Q_WRP(q, q->cons);
+}
+
+static bool queue_empty(struct arm_smmu_queue *q)
+{
+       return Q_IDX(q, q->prod) == Q_IDX(q, q->cons) &&
+              Q_WRP(q, q->prod) == Q_WRP(q, q->cons);
+}
+
+static void queue_sync_cons(struct arm_smmu_queue *q)
+{
+       q->cons = mmio_read32(q->cons_reg);
+}
+
+static bool queue_error(struct arm_smmu_queue *q)
+{
+       return mmio_read32(q->cons_reg) & 0x1;
+}
+
+static void queue_inc_prod(struct arm_smmu_queue *q)
+{
+       u32 prod = (Q_WRP(q, q->prod) | Q_IDX(q, q->prod)) + 1;
+
+       q->prod = Q_OVF(q, q->prod) | Q_WRP(q, prod) | Q_IDX(q, prod);
+       mmio_write32(q->prod_reg, q->prod);
+}
+
+static void queue_write(__u64 *dst, __u64 *src, u32 n_dwords)
+{
+       int i;
+
+       for (i = 0; i < n_dwords; ++i)
+               *dst++ = *src++;
+       dsb(ish);
+}
+
+static int queue_insert_raw(struct arm_smmu_queue *q, __u64 *ent)
+{
+       while (queue_full(q))
+       {}
+
+       queue_write(Q_ENT(q, q->prod), ent, q->ent_dwords);
+       queue_inc_prod(q);
+       while (!queue_empty(q) && !queue_error(q)) {
+               queue_sync_cons(q);
+       }
+       return 0;
+}
+
+/* High-level queue accessors */
+static int arm_smmu_cmdq_build_cmd(__u64 *cmd, struct arm_smmu_cmdq_ent *ent)
+{
+       u64 vmid = (u64)this_cell()->config->id;
+       memset(cmd, 0, CMDQ_ENT_DWORDS << 3);
+       cmd[0] |= FIELD_PREP(CMDQ_0_OP, ent->opcode);
+
+       switch (ent->opcode) {
+       case CMDQ_OP_TLBI_EL2_ALL:
+       case CMDQ_OP_TLBI_NSNH_ALL:
+               break;
+       case CMDQ_OP_CFGI_STE:
+               cmd[0] |= FIELD_PREP(CMDQ_CFGI_0_SID, ent->cfgi.sid);
+               cmd[1] |= FIELD_PREP(CMDQ_CFGI_1_LEAF, ent->cfgi.leaf);
+               break;
+       case CMDQ_OP_CFGI_ALL:
+               /* Cover the entire SID range */
+               cmd[1] |= FIELD_PREP(CMDQ_CFGI_1_RANGE, 31);
+               break;
+       case CMDQ_OP_TLBI_NH_VA:
+               cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_ASID, ent->tlbi.asid);
+               cmd[1] |= FIELD_PREP(CMDQ_TLBI_1_LEAF, ent->tlbi.leaf);
+               cmd[1] |= ent->tlbi.addr & CMDQ_TLBI_1_VA_MASK;
+               break;
+       case CMDQ_OP_TLBI_S2_IPA:
+               cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_VMID, vmid);
+               cmd[1] |= FIELD_PREP(CMDQ_TLBI_1_LEAF, ent->tlbi.leaf);
+               cmd[1] |= ent->tlbi.addr & CMDQ_TLBI_1_IPA_MASK;
+               break;
+       case CMDQ_OP_TLBI_NH_ASID:
+               cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_ASID, ent->tlbi.asid);
+               /* Fallthrough */
+       case CMDQ_OP_TLBI_S12_VMALL:
+               cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_VMID, vmid);
+               break;
+       case CMDQ_OP_CMD_SYNC:
+               cmd[0] |= FIELD_PREP(CMDQ_SYNC_0_CS, CMDQ_SYNC_0_CS_NONE);
+               cmd[0] |= FIELD_PREP(CMDQ_SYNC_0_MSH, ARM_SMMU_SH_NSH);
+               cmd[0] |= FIELD_PREP(CMDQ_SYNC_0_MSIATTR, 0x0);
+               /*
+                * Commands are written little-endian, but we want the SMMU to
+                * receive MSIData, and thus write it back to memory, in CPU
+                * byte order, so big-endian needs an extra byteswap here.
+                */
+               cmd[0] |= FIELD_PREP(CMDQ_SYNC_0_MSIDATA, 0x0);
+               cmd[1] |= 0x0 & CMDQ_SYNC_1_MSIADDR_MASK;
+               break;
+       default:
+               return -ENOENT;
+       }
+
+       return 0;
+}
+
+static void arm_smmu_cmdq_insert_cmd(struct arm_smmu_device *smmu, __u64 *cmd)
+{
+       struct arm_smmu_queue *q = &smmu->cmdq.q;
+
+       queue_insert_raw(q, cmd);
+}
+
+static void arm_smmu_cmdq_issue_cmd(struct arm_smmu_device *smmu,
+                                   struct arm_smmu_cmdq_ent *ent)
+{
+       u64 cmd[CMDQ_ENT_DWORDS];
+
+       if (arm_smmu_cmdq_build_cmd(cmd, ent)) {
+               printk("WARN: SMMU ignoring unknown CMDQ opcode 0x%x\n",
+                        ent->opcode);
+               return;
+       }
+
+       spin_lock(&smmu->cmdq.lock);
+       arm_smmu_cmdq_insert_cmd(smmu, cmd);
+       spin_unlock(&smmu->cmdq.lock);
+}
+
+static void arm_smmu_cmdq_issue_sync(struct arm_smmu_device *smmu)
+{
+       struct arm_smmu_cmdq_ent ent = { .opcode = CMDQ_OP_CMD_SYNC };
+       u64 cmd[CMDQ_ENT_DWORDS];
+
+       arm_smmu_cmdq_build_cmd(cmd, &ent);
+
+       spin_lock(&smmu->cmdq.lock);
+       arm_smmu_cmdq_insert_cmd(smmu, cmd);
+       spin_unlock(&smmu->cmdq.lock);
+}
+
+/* Stream table manipulation functions */
+static void
+arm_smmu_write_strtab_l1_desc(__u64 *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;
+
+       /* Assuming running on Little endian cpu */
+       *dst = val;
+       dsb(ish);
+}
+
+static void arm_smmu_sync_ste_for_sid(struct arm_smmu_device *smmu, u32 sid)
+{
+       struct arm_smmu_cmdq_ent cmd = {
+               .opcode = CMDQ_OP_CFGI_STE,
+               .cfgi   = {
+                       .sid    = sid,
+                       .leaf   = true,
+               },
+       };
+
+       arm_smmu_cmdq_issue_cmd(smmu, &cmd);
+       arm_smmu_cmdq_issue_sync(smmu);
+}
+
+static void arm_smmu_write_strtab_ent(struct arm_smmu_device *smmu, u32 sid,
+                                     __u64 *dst, bool bypass, u32 vmid)
+{
+       struct paging_structures *pg_structs = &this_cell()->arch.mm;
+
+       /* Bypass */
+       if (bypass) {
+               dst[0] = FIELD_PREP(STRTAB_STE_0_CFG, STRTAB_STE_0_CFG_BYPASS);
+               dst[1] = FIELD_PREP(STRTAB_STE_1_SHCFG,
+                                   STRTAB_STE_1_SHCFG_INCOMING);
+               dst[2] = vmid;
+               dsb(ish);
+               if (smmu) {
+                       dst[0] |= STRTAB_STE_0_V;
+                       dsb(ish);
+                       arm_smmu_sync_ste_for_sid(smmu, sid);
+               }
+               return;
+       }
+
+       dst[2] = FIELD_PREP(STRTAB_STE_2_S2VMID, vmid) |
+                FIELD_PREP(STRTAB_STE_2_VTCR, VTCR_CELL) |
+                STRTAB_STE_2_S2PTW | STRTAB_STE_2_S2AA64 |
+                STRTAB_STE_2_S2R;
+
+       dst[3] = paging_hvirt2phys(pg_structs->root_table) &
+               STRTAB_STE_3_S2TTB_MASK;
+
+       dst[0] = FIELD_PREP(STRTAB_STE_0_CFG, STRTAB_STE_0_CFG_S2_TRANS);
+       dsb(ish);
+       dst[0] |= STRTAB_STE_0_V;
+
+       arm_smmu_sync_ste_for_sid(smmu, sid);
+}
+
+static void arm_smmu_init_bypass_stes(u64 *strtab, unsigned int nent)
+{
+       unsigned int i;
+
+       for (i = 0; i < nent; ++i) {
+               arm_smmu_write_strtab_ent(NULL, -1, strtab, true,
+                                         (u32)this_cell()->config->id);
+               strtab += STRTAB_STE_DWORDS;
+       }
+}
+
+static int arm_smmu_init_strtab_linear(struct arm_smmu_device *smmu)
+{
+       struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+       void *strtab;
+       u32 size;
+       u64 reg;
+
+       size = (1 << smmu->sid_bits) * (STRTAB_STE_DWORDS << 3);
+       strtab = page_alloc_aligned(&mem_pool, PAGES(size));
+       if (!strtab) {
+               printk("ERROR: SMMU failed to allocate l1 stream table (%u 
bytes)\n",
+                      size);
+               return -ENOMEM;
+       }
+       cfg->strtab_dma = paging_hvirt2phys(strtab);
+       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;
+
+       arm_smmu_init_bypass_stes(strtab, cfg->num_l1_ents);
+       return 0;
+}
+
+static int arm_smmu_init_l1_strtab(struct arm_smmu_device *smmu)
+{
+       struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+       u32 size = sizeof(*cfg->l1_desc) * cfg->num_l1_ents;
+       void *strtab = smmu->strtab_cfg.strtab;
+       unsigned int i;
+
+       cfg->l1_desc = page_alloc(&mem_pool, PAGES(size));
+       if (!cfg->l1_desc) {
+               printk("ERROR: SMMU failed to allocate l1 stream table desc\n");
+               return -ENOMEM;
+       }
+
+       for (i = 0; i < cfg->num_l1_ents; ++i) {
+               memset(&cfg->l1_desc[i], 0, sizeof(*cfg->l1_desc));
+               arm_smmu_write_strtab_l1_desc(strtab, &cfg->l1_desc[i]);
+               cfg->l1_desc[i].active_stes = 0;
+               strtab += STRTAB_L1_DESC_DWORDS << 3;
+       }
+
+       return 0;
+}
+
+static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu)
+{
+       struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+       u32 size, l1size;
+       void *strtab;
+       u64 reg;
+
+       /* Calculate the L1 size, capped to the SIDSIZE. */
+       size = STRTAB_L1_SZ_SHIFT - 3;
+       size = MIN(size, smmu->sid_bits - STRTAB_SPLIT);
+       cfg->num_l1_ents = 1 << size;
+
+       size += STRTAB_SPLIT;
+       if (size < smmu->sid_bits)
+               printk("WARN: SMMU 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 = page_alloc_aligned(&mem_pool, PAGES(l1size));
+       if (!strtab) {
+               printk("ERROR: SMMU failed to allocate l1 stream table (%u 
bytes)\n",
+                      size);
+               return -ENOMEM;
+       }
+       cfg->strtab_dma = paging_hvirt2phys(strtab);
+       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, STRTAB_SPLIT);
+       cfg->strtab_base_cfg = reg;
+
+       return arm_smmu_init_l1_strtab(smmu);
+}
+
+static 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;
+
+       return 0;
+}
+
+static int arm_smmu_init_one_queue(struct arm_smmu_device *smmu,
+                                  struct arm_smmu_queue *q,
+                                  unsigned long prod_off,
+                                  unsigned long cons_off,
+                                  unsigned long dwords)
+{
+       /* Queue size is capped to 4K. So allocate 1 page */
+       q->base = page_alloc(&mem_pool, 1);
+       if (!q->base) {
+               printk("ERROR: SMMU failed to allocate queue\n");
+               return -ENOMEM;
+       }
+       q->base_dma = paging_hvirt2phys(q->base);;
+
+       q->prod_reg     = smmu->base + prod_off;
+       q->cons_reg     = smmu->base + 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->max_n_shift);
+
+       mmio_write32(q->prod_reg, 0);
+       mmio_write32(q->cons_reg, 0);
+       q->prod = q->cons = 0;
+       return 0;
+}
+
+static int arm_smmu_init_queues(struct arm_smmu_device *smmu)
+{
+       int ret;
+
+       /* cmdq */
+       ret = arm_smmu_init_one_queue(smmu, &smmu->cmdq.q, ARM_SMMU_CMDQ_PROD,
+                                     ARM_SMMU_CMDQ_CONS, CMDQ_ENT_DWORDS);
+       if (ret)
+               return ret;
+
+       /* evtq */
+       ret = arm_smmu_init_one_queue(smmu, &smmu->evtq.q, ARM_SMMU_EVTQ_PROD,
+                                     ARM_SMMU_EVTQ_CONS, EVTQ_ENT_DWORDS);
+       if (ret)
+               return ret;
+
+       return ret;
+}
+
+static int arm_smmu_init_structures(struct arm_smmu_device *smmu)
+{
+       int ret;
+
+       ret = arm_smmu_init_queues(smmu);
+       if (ret)
+               return ret;
+
+       return arm_smmu_init_strtab(smmu);
+}
+
+static int arm_smmu_write_reg_sync(struct arm_smmu_device *smmu, u32 val,
+                                  unsigned int reg_off, unsigned int ack_off)
+{
+       u32 i, timeout = ARM_SMMU_SYNC_TIMEOUT;
+
+       mmio_write32(smmu->base + reg_off, val);
+       for (i = 0; i < timeout; i++) {
+               if (mmio_read32(smmu->base + ack_off) == val)
+                       return 0;
+       }
+
+       return -EINVAL;
+}
+
+static 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)
+               printk("ERROR: SMMU failed to clear cr0\n");
+
+       return ret;
+}
+
+static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
+{
+       struct arm_smmu_cmdq_ent cmd;
+       u32 reg, enables;
+       int ret;
+
+       /* Clear CR0 and sync (disables SMMU and queue processing) */
+       reg = mmio_read32(smmu->base + ARM_SMMU_CR0);
+       if (reg & CR0_SMMUEN)
+               printk("ERROR: SMMU currently enabled! Resetting...\n");
+
+       ret = arm_smmu_device_disable(smmu);
+       if (ret)
+               return ret;
+
+       /* CR1 (table and queue memory attributes) */
+       reg = FIELD_PREP(CR1_TABLE_SH, ARM_SMMU_SH_ISH) |
+             FIELD_PREP(CR1_TABLE_OC, CR1_CACHE_WB) |
+             FIELD_PREP(CR1_TABLE_IC, CR1_CACHE_WB) |
+             FIELD_PREP(CR1_QUEUE_SH, ARM_SMMU_SH_ISH) |
+             FIELD_PREP(CR1_QUEUE_OC, CR1_CACHE_WB) |
+             FIELD_PREP(CR1_QUEUE_IC, CR1_CACHE_WB);
+       mmio_write32(smmu->base + ARM_SMMU_CR1, reg);
+
+       /* Stream table */
+       mmio_write64(smmu->base + ARM_SMMU_STRTAB_BASE,
+                    smmu->strtab_cfg.strtab_base);
+       mmio_write32(smmu->base + ARM_SMMU_STRTAB_BASE_CFG,
+                    smmu->strtab_cfg.strtab_base_cfg);
+
+       /* Command queue */
+       mmio_write64(smmu->base + ARM_SMMU_CMDQ_BASE, smmu->cmdq.q.q_base);
+       mmio_write32(smmu->base + ARM_SMMU_CMDQ_PROD, smmu->cmdq.q.prod);
+       mmio_write32(smmu->base + ARM_SMMU_CMDQ_CONS, smmu->cmdq.q.cons);
+
+       enables = CR0_CMDQEN;
+       ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
+                                     ARM_SMMU_CR0ACK);
+       if (ret) {
+               printk("ERROR: SMMU failed to enable command queue\n");
+               return ret;
+       }
+
+       /* Invalidate any cached configuration */
+       cmd.opcode = CMDQ_OP_CFGI_ALL;
+       arm_smmu_cmdq_issue_cmd(smmu, &cmd);
+       arm_smmu_cmdq_issue_sync(smmu);
+
+       cmd.opcode = CMDQ_OP_TLBI_NSNH_ALL;
+       arm_smmu_cmdq_issue_cmd(smmu, &cmd);
+
+       /* Invalidate any stale TLB entries */
+       cmd.opcode = CMDQ_OP_TLBI_EL2_ALL;
+       arm_smmu_cmdq_issue_cmd(smmu, &cmd);
+       arm_smmu_cmdq_issue_sync(smmu);
+
+       /* Event queue */
+       mmio_write64(smmu->base + ARM_SMMU_EVTQ_BASE, smmu->evtq.q.q_base);
+       mmio_write32(smmu->base + ARM_SMMU_EVTQ_PROD, smmu->evtq.q.prod);
+       mmio_write32(smmu->base + ARM_SMMU_EVTQ_CONS, smmu->evtq.q.cons);
+
+       enables |= CR0_EVTQEN;
+       ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
+                                     ARM_SMMU_CR0ACK);
+       if (ret) {
+               printk("ERROR: SMMU failed to enable event queue\n");
+               return ret;
+       }
+
+       /* ToDo: Add support for PRI queue and IRQs  */
+
+       enables |= CR0_SMMUEN;
+       ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
+                                     ARM_SMMU_CR0ACK);
+       if (ret) {
+               printk("ERROR: SMMU failed to enable SMMU interface\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static int arm_smmu_device_init_features(struct arm_smmu_device *smmu)
+{
+       u32 reg;
+
+       /* IDR0 */
+       reg = mmio_read32(smmu->base + ARM_SMMU_IDR0);
+
+       smmu->features = 0;
+       /* 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_S2P)) {
+               printk("ERROR: SMMU stage2 translations not supported\n");
+               return -ENXIO;
+       }
+
+       /* IDR1 */
+       reg = mmio_read32(smmu->base + ARM_SMMU_IDR1);
+       if (reg & (IDR1_TABLES_PRESET | IDR1_QUEUES_PRESET | IDR1_REL)) {
+               printk("ERROR: SMMU embedded implementation not supported\n");
+               return -ENXIO;
+       }
+
+       /* Queue sizes, capped at 4k */
+       smmu->cmdq.q.max_n_shift = MIN(CMDQ_MAX_SZ_SHIFT,
+                                      FIELD_GET(IDR1_CMDQS, reg));
+       if (!smmu->cmdq.q.max_n_shift) {
+               printk("ERROR: SMMU unit-length command queue not supported\n");
+               return -ENXIO;
+       }
+       smmu->evtq.q.max_n_shift = MIN(EVTQ_MAX_SZ_SHIFT,
+                                      FIELD_GET(IDR1_EVTQS, reg));
+
+       /* SID sizes */
+       smmu->sid_bits = 16;
+
+       /*
+        * If the SMMU supports fewer bits than would fill a single L2 stream
+        * table, use a linear table instead.
+        */
+       if (smmu->sid_bits <= STRTAB_SPLIT)
+               smmu->features &= ~ARM_SMMU_FEAT_2_LVL_STRTAB;
+
+       return 0;
+}
+
+static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid)
+{
+       struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+       struct arm_smmu_strtab_l1_desc *desc;
+       void *strtab;
+       u32 size;
+
+       desc = &cfg->l1_desc[sid >> STRTAB_SPLIT];
+       if (desc->l2ptr) {
+               desc->active_stes++;
+               return 0;
+       }
+
+       size = 1 << (STRTAB_SPLIT + STRTAB_STE_DWORDS_BITS + 3);
+       strtab = &cfg->strtab[(sid >> STRTAB_SPLIT) * STRTAB_L1_DESC_DWORDS];
+
+       desc->span = STRTAB_SPLIT + 1;
+       desc->l2ptr = page_alloc_aligned(&mem_pool, PAGES(size));
+       if (!desc->l2ptr) {
+               printk("ERROR: SMMU failed to allocate l2 stream table (%u 
bytes)\n",
+                      size);
+               return -ENOMEM;
+       }
+       desc->l2ptr_dma = paging_hvirt2phys(desc->l2ptr);
+       desc->active_stes = 1;
+       arm_smmu_init_bypass_stes(desc->l2ptr, 1 << STRTAB_SPLIT);
+       arm_smmu_write_strtab_l1_desc(strtab, desc);
+
+       return 0;
+}
+
+static void arm_smmu_uninit_l2_strtab(struct arm_smmu_device *smmu, u32 sid)
+{
+       struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+       struct arm_smmu_strtab_l1_desc *desc;
+       void *strtab;
+       u32 size;
+
+       desc = &cfg->l1_desc[sid >> STRTAB_SPLIT];
+
+       desc->active_stes--;
+       if (desc->active_stes)
+               return;
+
+       size = 1 << (STRTAB_SPLIT + STRTAB_STE_DWORDS_BITS + 3);
+       page_free(&mem_pool, desc->l2ptr, PAGES(size));
+       desc->l2ptr = NULL;
+       desc->l2ptr_dma = 0;
+       desc->span = 0;
+       strtab = &cfg->strtab[(sid >> STRTAB_SPLIT) * STRTAB_L1_DESC_DWORDS];
+       arm_smmu_write_strtab_l1_desc(strtab, desc);
+
+       return;
+}
+
+static __u64 *arm_smmu_get_step_for_sid(struct arm_smmu_device *smmu, u32 sid)
+{
+       struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+       __u64 *step;
+
+       if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) {
+               struct arm_smmu_strtab_l1_desc *l1_desc;
+               int idx;
+
+               /* Two-level walk */
+               idx = (sid >> STRTAB_SPLIT) * STRTAB_L1_DESC_DWORDS;
+               l1_desc = &cfg->l1_desc[idx];
+               idx = (sid & ((1 << STRTAB_SPLIT) - 1)) * STRTAB_STE_DWORDS;
+               step = &l1_desc->l2ptr[idx];
+       } else {
+               /* Simple linear lookup */
+               step = &cfg->strtab[sid * STRTAB_STE_DWORDS];
+       }
+
+       return step;
+}
+
+static int arm_smmu_init_ste(struct arm_smmu_device *smmu, u32 sid, u32 vmid)
+{
+       __u64 *step;
+
+       if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)
+               arm_smmu_init_l2_strtab(smmu, sid);
+
+       step = arm_smmu_get_step_for_sid(smmu, sid);
+       arm_smmu_write_strtab_ent(smmu, sid, step, false, vmid);
+
+       return 0;
+}
+
+static void arm_smmu_uninit_ste(struct arm_smmu_device *smmu, u32 sid, u32 
vmid)
+{
+       __u64 *step;
+
+       step = arm_smmu_get_step_for_sid(smmu, sid);
+       arm_smmu_write_strtab_ent(smmu, sid, step, true, vmid);
+
+       if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)
+               arm_smmu_uninit_l2_strtab(smmu, sid);
+}
+
+static int arm_smmuv3_cell_init(struct cell *cell)
+{
+       struct jailhouse_iommu *iommu;
+       struct arm_smmu_cmdq_ent cmd;
+       int ret, i, sid;
+
+       for (i = 0; i < JAILHOUSE_MAX_IOMMU_UNITS; i++) {
+               iommu = &system_config->platform_info.arm.iommu_units[i];
+               if (iommu->type != JAILHOUSE_IOMMU_SMMUV3)
+                       continue;
+
+               for (sid = 0; sid < ARRAY_SIZE(cell->config->sids); sid++) {
+                       if (cell->config->sids[sid] == 0)
+                               continue;
+                       ret = arm_smmu_init_ste(&smmu[i],
+                                               cell->config->sids[sid],
+                                               cell->config->id);
+                       if (ret) {
+                               printk("ERROR: SMMU INIT ste failed: sid = 
%d\n",
+                                      cell->config->sids[sid]);
+                               return ret;
+                       }
+               }
+       }
+
+       cmd.opcode      = CMDQ_OP_TLBI_S12_VMALL;
+       cmd.tlbi.vmid   = cell->config->id;
+       arm_smmu_cmdq_issue_cmd(smmu, &cmd);
+       arm_smmu_cmdq_issue_sync(smmu);
+
+       return 0;
+}
+
+static void arm_smmuv3_cell_exit(struct cell *cell)
+{
+       struct jailhouse_iommu *iommu;
+       int i, sid;
+
+       for (i = 0; i < JAILHOUSE_MAX_IOMMU_UNITS; i++) {
+               iommu = &system_config->platform_info.arm.iommu_units[i];
+               if (iommu->type != JAILHOUSE_IOMMU_SMMUV3)
+                       continue;
+
+               for (sid = 0; sid < ARRAY_SIZE(cell->config->sids); sid++) {
+                       if (cell->config->sids[sid] == 0)
+                               continue;
+                       arm_smmu_uninit_ste(&smmu[i],
+                                           cell->config->sids[sid],
+                                           cell->config->id);
+               }
+       }
+
+}
+
+static int arm_smmuv3_init(void)
+{
+       struct jailhouse_iommu *iommu;
+       int ret, i;
+
+       for (i = 0; i < JAILHOUSE_MAX_IOMMU_UNITS; i++) {
+               iommu = &system_config->platform_info.arm.iommu_units[i];
+               if (iommu->type != JAILHOUSE_IOMMU_SMMUV3)
+                       continue;
+
+               smmu[i].base = paging_map_device(iommu->smmuv3.smmu_base,
+                                              iommu->smmuv3.smmu_size);
+
+               /* ToDo: irq allocation*/
+
+               ret = arm_smmu_device_init_features(&smmu[i]);
+               if (ret)
+                       return ret;
+
+               ret = arm_smmu_init_structures(&smmu[i]);
+               if (ret)
+                       return ret;
+
+               /* Reset the device */
+               ret = arm_smmu_device_reset(&smmu[i]);
+               if (ret)
+                       return ret;
+       }
+
+       return arm_smmuv3_cell_init(&root_cell);
+}
+
+DEFINE_UNIT_SHUTDOWN_STUB(arm_smmuv3);
+DEFINE_UNIT_MMIO_COUNT_REGIONS_STUB(arm_smmuv3);
+DEFINE_UNIT(arm_smmuv3, "ARM SMMU v3");
diff --git a/hypervisor/include/jailhouse/entry.h 
b/hypervisor/include/jailhouse/entry.h
index 26360a6e..59a09be5 100644
--- a/hypervisor/include/jailhouse/entry.h
+++ b/hypervisor/include/jailhouse/entry.h
@@ -21,6 +21,7 @@
 #define EPERM          1
 #define ENOENT         2
 #define EIO            5
+#define        ENXIO           6
 #define E2BIG          7
 #define ENOMEM         12
 #define EBUSY          16
-- 
2.21.0

-- 
You received this message because you are subscribed to the Google Groups 
"Jailhouse" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to