Add the hexagon TLB QOM device model.

Signed-off-by: Brian Cain <[email protected]>

Reviewed-by: Taylor Simpson <[email protected]>
---
 include/hw/hexagon/hexagon_tlb.h |  45 +++
 target/hexagon/cpu.h             |   4 +
 hw/hexagon/hexagon_tlb.c         | 463 +++++++++++++++++++++++++++++++
 target/hexagon/cpu.c             |   5 +
 4 files changed, 517 insertions(+)
 create mode 100644 include/hw/hexagon/hexagon_tlb.h
 create mode 100644 hw/hexagon/hexagon_tlb.c

diff --git a/include/hw/hexagon/hexagon_tlb.h b/include/hw/hexagon/hexagon_tlb.h
new file mode 100644
index 00000000000..bcb387aa24d
--- /dev/null
+++ b/include/hw/hexagon/hexagon_tlb.h
@@ -0,0 +1,45 @@
+/*
+ * Hexagon TLB QOM Device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_HEXAGON_TLB_H
+#define HW_HEXAGON_TLB_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+#include "exec/hwaddr.h"
+#include "exec/mmu-access-type.h"
+#define TYPE_HEXAGON_TLB "hexagon-tlb"
+OBJECT_DECLARE_SIMPLE_TYPE(HexagonTLBState, HEXAGON_TLB)
+
+struct HexagonTLBState {
+    SysBusDevice parent_obj;
+
+    uint32_t num_entries;
+    uint64_t *entries;
+};
+
+uint64_t hexagon_tlb_read(HexagonTLBState *tlb, uint32_t index);
+void hexagon_tlb_write(HexagonTLBState *tlb, uint32_t index, uint64_t value);
+
+bool hexagon_tlb_find_match(HexagonTLBState *tlb, uint32_t asid,
+                            uint32_t VA, MMUAccessType access_type,
+                            hwaddr *PA, int *prot, uint64_t *size,
+                            int32_t *excp, int *cause_code, int mmu_idx);
+
+uint32_t hexagon_tlb_lookup(HexagonTLBState *tlb, uint32_t asid,
+                            uint32_t VA, int *cause_code);
+
+int hexagon_tlb_check_overlap(HexagonTLBState *tlb, uint64_t entry,
+                              uint64_t index);
+
+void hexagon_tlb_dump(HexagonTLBState *tlb);
+
+bool hexagon_tlb_dump_entry(FILE *f, uint64_t entry);
+
+uint32_t hexagon_tlb_get_num_entries(HexagonTLBState *tlb);
+
+#endif /* HW_HEXAGON_TLB_H */
diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h
index 9eb2d1bbabe..e39e6e39fec 100644
--- a/target/hexagon/cpu.h
+++ b/target/hexagon/cpu.h
@@ -46,6 +46,7 @@
 #define REG_WRITES_MAX 32
 #define PRED_WRITES_MAX 5                   /* 4 insns + endloop */
 #define VSTORES_MAX 2
+#define MAX_TLB_ENTRIES 1024
 
 #define CPU_RESOLVING_TYPE TYPE_HEXAGON_CPU
 #ifndef CONFIG_USER_ONLY
@@ -174,6 +175,9 @@ struct ArchCPU {
     bool lldb_compat;
     target_ulong lldb_stack_adjust;
     bool short_circuit;
+#ifndef CONFIG_USER_ONLY
+    struct HexagonTLBState *tlb;
+#endif
 };
 
 #include "cpu_bits.h"
diff --git a/hw/hexagon/hexagon_tlb.c b/hw/hexagon/hexagon_tlb.c
new file mode 100644
index 00000000000..90f319f56d3
--- /dev/null
+++ b/hw/hexagon/hexagon_tlb.c
@@ -0,0 +1,463 @@
+/*
+ * Hexagon TLB QOM Device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/hexagon/hexagon_tlb.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/resettable.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "target/hexagon/cpu.h"
+#include "target/hexagon/cpu_bits.h"
+
+/* PTE (TLB entry) field extraction */
+#define GET_PTE_PPD(entry)   extract64((entry),  0, 24)
+#define GET_PTE_C(entry)     extract64((entry), 24,  4)
+#define GET_PTE_U(entry)     extract64((entry), 28,  1)
+#define GET_PTE_R(entry)     extract64((entry), 29,  1)
+#define GET_PTE_W(entry)     extract64((entry), 30,  1)
+#define GET_PTE_X(entry)     extract64((entry), 31,  1)
+#define GET_PTE_VPN(entry)   extract64((entry), 32, 20)
+#define GET_PTE_ASID(entry)  extract64((entry), 52,  7)
+#define GET_PTE_ATR0(entry)  extract64((entry), 59,  1)
+#define GET_PTE_ATR1(entry)  extract64((entry), 60,  1)
+#define GET_PTE_PA35(entry)  extract64((entry), 61,  1)
+#define GET_PTE_G(entry)     extract64((entry), 62,  1)
+#define GET_PTE_V(entry)     extract64((entry), 63,  1)
+
+/* PPD (physical page descriptor) */
+static inline uint64_t GET_PPD(uint64_t entry)
+{
+    return GET_PTE_PPD(entry) | (GET_PTE_PA35(entry) << 24);
+}
+
+#define NO_ASID      (1 << 8)
+
+typedef enum {
+    PGSIZE_4K,
+    PGSIZE_16K,
+    PGSIZE_64K,
+    PGSIZE_256K,
+    PGSIZE_1M,
+    PGSIZE_4M,
+    PGSIZE_16M,
+    PGSIZE_64M,
+    PGSIZE_256M,
+    PGSIZE_1G,
+    NUM_PGSIZE_TYPES
+} tlb_pgsize_t;
+
+static const char *pgsize_str[NUM_PGSIZE_TYPES] = {
+    "4K",
+    "16K",
+    "64K",
+    "256K",
+    "1M",
+    "4M",
+    "16M",
+    "64M",
+    "256M",
+    "1G",
+};
+
+#define INVALID_MASK 0xffffffffLL
+
+static const uint64_t encmask_2_mask[] = {
+    0x0fffLL,                           /* 4k,   0000 */
+    0x3fffLL,                           /* 16k,  0001 */
+    0xffffLL,                           /* 64k,  0010 */
+    0x3ffffLL,                          /* 256k, 0011 */
+    0xfffffLL,                          /* 1m,   0100 */
+    0x3fffffLL,                         /* 4m,   0101 */
+    0xffffffLL,                         /* 16m,  0110 */
+    0x3ffffffLL,                        /* 64m,  0111 */
+    0xfffffffLL,                        /* 256m, 1000 */
+    0x3fffffffLL,                       /* 1g,   1001 */
+    INVALID_MASK,                       /* RSVD, 0111 */
+};
+
+static inline tlb_pgsize_t hex_tlb_pgsize_type(uint64_t entry)
+{
+    if (entry == 0) {
+        qemu_log_mask(CPU_LOG_MMU, "%s: Supplied TLB entry was 0!\n",
+                      __func__);
+        return 0;
+    }
+    tlb_pgsize_t size = ctz64(entry);
+    g_assert(size < NUM_PGSIZE_TYPES);
+    return size;
+}
+
+static inline uint64_t hex_tlb_page_size_bytes(uint64_t entry)
+{
+    return 1ull << (TARGET_PAGE_BITS + 2 * hex_tlb_pgsize_type(entry));
+}
+
+static inline uint64_t hex_tlb_phys_page_num(uint64_t entry)
+{
+    uint32_t ppd = GET_PPD(entry);
+    return ppd >> 1;
+}
+
+static inline uint64_t hex_tlb_phys_addr(uint64_t entry)
+{
+    uint64_t pagemask = encmask_2_mask[hex_tlb_pgsize_type(entry)];
+    uint64_t pagenum = hex_tlb_phys_page_num(entry);
+    uint64_t PA = (pagenum << TARGET_PAGE_BITS) & (~pagemask);
+    return PA;
+}
+
+static inline uint64_t hex_tlb_virt_addr(uint64_t entry)
+{
+    return (uint64_t)GET_PTE_VPN(entry) << TARGET_PAGE_BITS;
+}
+
+bool hexagon_tlb_dump_entry(FILE *f, uint64_t entry)
+{
+    if (GET_PTE_V(entry)) {
+        fprintf(f, "0x%016" PRIx64 ": ", entry);
+        uint64_t PA = hex_tlb_phys_addr(entry);
+        uint64_t VA = hex_tlb_virt_addr(entry);
+        fprintf(f, "V:%" PRId64 " G:%" PRId64
+                " A1:%" PRId64 " A0:%" PRId64,
+                GET_PTE_V(entry),
+                GET_PTE_G(entry),
+                GET_PTE_ATR1(entry),
+                GET_PTE_ATR0(entry));
+        fprintf(f, " ASID:0x%02" PRIx64 " VA:0x%08" PRIx64,
+                GET_PTE_ASID(entry), VA);
+        fprintf(f,
+                " X:%" PRId64 " W:%" PRId64 " R:%" PRId64
+                " U:%" PRId64 " C:%" PRId64,
+                GET_PTE_X(entry),
+                GET_PTE_W(entry),
+                GET_PTE_R(entry),
+                GET_PTE_U(entry),
+                GET_PTE_C(entry));
+        fprintf(f, " PA:0x%09" PRIx64 " SZ:%s (0x%" PRIx64 ")", PA,
+                pgsize_str[hex_tlb_pgsize_type(entry)],
+                hex_tlb_page_size_bytes(entry));
+        fprintf(f, "\n");
+        return true;
+    }
+
+    /* Not valid */
+    return false;
+}
+
+static inline bool hex_tlb_entry_match_noperm(uint64_t entry, uint32_t asid,
+                                              uint64_t VA)
+{
+    if (GET_PTE_V(entry)) {
+        if (GET_PTE_G(entry)) {
+            /* Global entry - ignore ASID */
+        } else if (asid != NO_ASID) {
+            uint32_t tlb_asid = GET_PTE_ASID(entry);
+            if (tlb_asid != asid) {
+                return false;
+            }
+        }
+
+        uint64_t page_size = hex_tlb_page_size_bytes(entry);
+        uint64_t page_start =
+            ROUND_DOWN(hex_tlb_virt_addr(entry), page_size);
+        if (page_start <= VA && VA < page_start + page_size) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static inline void hex_tlb_entry_get_perm(uint64_t entry,
+                                          MMUAccessType access_type,
+                                          int mmu_idx, int *prot,
+                                          int32_t *excp, int *cause_code)
+{
+    bool perm_x = GET_PTE_X(entry);
+    bool perm_w = GET_PTE_W(entry);
+    bool perm_r = GET_PTE_R(entry);
+    bool perm_u = GET_PTE_U(entry);
+    bool user_idx = mmu_idx == MMU_USER_IDX;
+
+    if (mmu_idx == MMU_KERNEL_IDX) {
+        *prot = PAGE_VALID | PAGE_READ | PAGE_WRITE | PAGE_EXEC;
+        return;
+    }
+
+    *prot = PAGE_VALID;
+    switch (access_type) {
+    case MMU_INST_FETCH:
+        if (user_idx && !perm_u) {
+            *excp = HEX_EVENT_PRECISE;
+            *cause_code = HEX_CAUSE_FETCH_NO_UPAGE;
+        } else if (!perm_x) {
+            *excp = HEX_EVENT_PRECISE;
+            *cause_code = HEX_CAUSE_FETCH_NO_XPAGE;
+        }
+        break;
+    case MMU_DATA_LOAD:
+        if (user_idx && !perm_u) {
+            *excp = HEX_EVENT_PRECISE;
+            *cause_code = HEX_CAUSE_PRIV_NO_UREAD;
+        } else if (!perm_r) {
+            *excp = HEX_EVENT_PRECISE;
+            *cause_code = HEX_CAUSE_PRIV_NO_READ;
+        }
+        break;
+    case MMU_DATA_STORE:
+        if (user_idx && !perm_u) {
+            *excp = HEX_EVENT_PRECISE;
+            *cause_code = HEX_CAUSE_PRIV_NO_UWRITE;
+        } else if (!perm_w) {
+            *excp = HEX_EVENT_PRECISE;
+            *cause_code = HEX_CAUSE_PRIV_NO_WRITE;
+        }
+        break;
+    }
+
+    if (!user_idx || perm_u) {
+        if (perm_x) {
+            *prot |= PAGE_EXEC;
+        }
+        if (perm_r) {
+            *prot |= PAGE_READ;
+        }
+        if (perm_w) {
+            *prot |= PAGE_WRITE;
+        }
+    }
+}
+
+static inline bool hex_tlb_entry_match(uint64_t entry, uint8_t asid,
+                                       uint32_t VA,
+                                       MMUAccessType access_type, hwaddr *PA,
+                                       int *prot, uint64_t *size,
+                                       int32_t *excp, int *cause_code,
+                                       int mmu_idx)
+{
+    if (hex_tlb_entry_match_noperm(entry, asid, VA)) {
+        hex_tlb_entry_get_perm(entry, access_type, mmu_idx, prot, excp,
+                               cause_code);
+        *PA = hex_tlb_phys_addr(entry);
+        *size = hex_tlb_page_size_bytes(entry);
+        return true;
+    }
+    return false;
+}
+
+static bool hex_tlb_is_match(uint64_t entry1, uint64_t entry2,
+                             bool consider_gbit)
+{
+    bool valid1 = GET_PTE_V(entry1);
+    bool valid2 = GET_PTE_V(entry2);
+    uint64_t size1 = hex_tlb_page_size_bytes(entry1);
+    uint64_t vaddr1 = ROUND_DOWN(hex_tlb_virt_addr(entry1), size1);
+    uint64_t size2 = hex_tlb_page_size_bytes(entry2);
+    uint64_t vaddr2 = ROUND_DOWN(hex_tlb_virt_addr(entry2), size2);
+    int asid1 = GET_PTE_ASID(entry1);
+    int asid2 = GET_PTE_ASID(entry2);
+    bool gbit1 = GET_PTE_G(entry1);
+    bool gbit2 = GET_PTE_G(entry2);
+
+    if (!valid1 || !valid2) {
+        return false;
+    }
+
+    if (((vaddr1 <= vaddr2) && (vaddr2 < (vaddr1 + size1))) ||
+        ((vaddr2 <= vaddr1) && (vaddr1 < (vaddr2 + size2)))) {
+        if (asid1 == asid2) {
+            return true;
+        }
+        if ((consider_gbit && gbit1) || gbit2) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/* Public API */
+
+uint64_t hexagon_tlb_read(HexagonTLBState *tlb, uint32_t index)
+{
+    g_assert(index < tlb->num_entries);
+    return tlb->entries[index];
+}
+
+void hexagon_tlb_write(HexagonTLBState *tlb, uint32_t index, uint64_t value)
+{
+    g_assert(index < tlb->num_entries);
+    tlb->entries[index] = value;
+}
+
+bool hexagon_tlb_find_match(HexagonTLBState *tlb, uint32_t asid,
+                            uint32_t VA, MMUAccessType access_type,
+                            hwaddr *PA, int *prot, uint64_t *size,
+                            int32_t *excp, int *cause_code, int mmu_idx)
+{
+    *PA = 0;
+    *prot = 0;
+    *size = 0;
+    *excp = 0;
+    *cause_code = 0;
+
+    for (uint32_t i = 0; i < tlb->num_entries; i++) {
+        if (hex_tlb_entry_match(tlb->entries[i], asid, VA, access_type,
+                                PA, prot, size, excp, cause_code, mmu_idx)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+uint32_t hexagon_tlb_lookup(HexagonTLBState *tlb, uint32_t asid,
+                            uint32_t VA, int *cause_code)
+{
+    uint32_t not_found = 0x80000000;
+    uint32_t idx = not_found;
+
+    for (uint32_t i = 0; i < tlb->num_entries; i++) {
+        uint64_t entry = tlb->entries[i];
+        if (hex_tlb_entry_match_noperm(entry, asid, VA)) {
+            if (idx != not_found) {
+                *cause_code = HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH;
+                break;
+            }
+            idx = i;
+        }
+    }
+
+    if (idx == not_found) {
+        qemu_log_mask(CPU_LOG_MMU,
+                      "%s: 0x%" PRIx32 ", 0x%08" PRIx32 " => NOT FOUND\n",
+                      __func__, asid, VA);
+    } else {
+        qemu_log_mask(CPU_LOG_MMU,
+                      "%s: 0x%" PRIx32 ", 0x%08" PRIx32 " => %d\n",
+                      __func__, asid, VA, idx);
+    }
+
+    return idx;
+}
+
+/*
+ * Return codes:
+ * 0 or positive             index of match
+ * -1                        multiple matches
+ * -2                        no match
+ */
+int hexagon_tlb_check_overlap(HexagonTLBState *tlb, uint64_t entry,
+                              uint64_t index)
+{
+    int matches = 0;
+    int last_match = 0;
+
+    for (uint32_t i = 0; i < tlb->num_entries; i++) {
+        if (hex_tlb_is_match(entry, tlb->entries[i], false)) {
+            matches++;
+            last_match = i;
+        }
+    }
+
+    if (matches == 1) {
+        return last_match;
+    }
+    if (matches == 0) {
+        return -2;
+    }
+    return -1;
+}
+
+void hexagon_tlb_dump(HexagonTLBState *tlb)
+{
+    for (uint32_t i = 0; i < tlb->num_entries; i++) {
+        hexagon_tlb_dump_entry(stdout, tlb->entries[i]);
+    }
+}
+
+uint32_t hexagon_tlb_get_num_entries(HexagonTLBState *tlb)
+{
+    return tlb->num_entries;
+}
+
+/* QOM lifecycle */
+
+static void hexagon_tlb_init(Object *obj)
+{
+}
+
+static void hexagon_tlb_realize(DeviceState *dev, Error **errp)
+{
+    HexagonTLBState *s = HEXAGON_TLB(dev);
+
+    if (s->num_entries == 0 || s->num_entries > MAX_TLB_ENTRIES) {
+        error_setg(errp, "Invalid TLB num-entries: %" PRIu32,
+                   s->num_entries);
+        return;
+    }
+    s->entries = g_new0(uint64_t, s->num_entries);
+}
+
+static void hexagon_tlb_finalize(Object *obj)
+{
+    HexagonTLBState *s = HEXAGON_TLB(obj);
+    g_free(s->entries);
+    s->entries = NULL;
+}
+
+static void hexagon_tlb_reset_hold(Object *obj, ResetType type)
+{
+    HexagonTLBState *s = HEXAGON_TLB(obj);
+    if (s->entries) {
+        memset(s->entries, 0, sizeof(uint64_t) * s->num_entries);
+    }
+}
+
+static const VMStateDescription vmstate_hexagon_tlb = {
+    .name = "hexagon-tlb",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(num_entries, HexagonTLBState),
+        VMSTATE_VARRAY_UINT32_ALLOC(entries, HexagonTLBState, num_entries,
+                                    0, vmstate_info_uint64, uint64_t),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static const Property hexagon_tlb_properties[] = {
+    DEFINE_PROP_UINT32("num-entries", HexagonTLBState, num_entries,
+                       MAX_TLB_ENTRIES),
+};
+
+static void hexagon_tlb_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->realize = hexagon_tlb_realize;
+    rc->phases.hold = hexagon_tlb_reset_hold;
+    dc->vmsd = &vmstate_hexagon_tlb;
+    dc->user_creatable = false;
+    device_class_set_props(dc, hexagon_tlb_properties);
+}
+
+static const TypeInfo hexagon_tlb_info = {
+    .name = TYPE_HEXAGON_TLB,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(HexagonTLBState),
+    .instance_init = hexagon_tlb_init,
+    .instance_finalize = hexagon_tlb_finalize,
+    .class_init = hexagon_tlb_class_init,
+};
+
+static void hexagon_tlb_register_types(void)
+{
+    type_register_static(&hexagon_tlb_info);
+}
+
+type_init(hexagon_tlb_register_types)
diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c
index b1317f83ef4..32d158684a0 100644
--- a/target/hexagon/cpu.c
+++ b/target/hexagon/cpu.c
@@ -23,6 +23,7 @@
 #include "qapi/error.h"
 #include "hw/core/qdev-properties.h"
 #include "fpu/softfloat-helpers.h"
+#include "hw/hexagon/hexagon_tlb.h"
 #include "tcg/tcg.h"
 #include "exec/gdbstub.h"
 #include "accel/tcg/cpu-ops.h"
@@ -50,6 +51,10 @@ static ObjectClass *hexagon_cpu_class_by_name(const char 
*cpu_model)
 }
 
 static const Property hexagon_cpu_properties[] = {
+#if !defined(CONFIG_USER_ONLY)
+    DEFINE_PROP_LINK("tlb", HexagonCPU, tlb, TYPE_HEXAGON_TLB,
+                     HexagonTLBState *),
+#endif
     DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, lldb_compat, false),
     DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, lldb_stack_adjust, 0,
                          qdev_prop_uint32, target_ulong),
-- 
2.34.1

Reply via email to