summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hw/alpha/typhoon.c202
-rw-r--r--target-alpha/helper.h2
-rw-r--r--target-alpha/sys_helper.c17
-rw-r--r--target-alpha/translate.c82
4 files changed, 264 insertions, 39 deletions
diff --git a/hw/alpha/typhoon.c b/hw/alpha/typhoon.c
index b7fb04406c..245004530c 100644
--- a/hw/alpha/typhoon.c
+++ b/hw/alpha/typhoon.c
@@ -26,9 +26,9 @@ typedef struct TyphoonCchip {
} TyphoonCchip;
typedef struct TyphoonWindow {
- uint32_t base_addr;
- uint32_t mask;
- uint32_t translated_base_pfn;
+ uint64_t wba;
+ uint64_t wsm;
+ uint64_t tba;
} TyphoonWindow;
typedef struct TyphoonPchip {
@@ -37,6 +37,10 @@ typedef struct TyphoonPchip {
MemoryRegion reg_mem;
MemoryRegion reg_io;
MemoryRegion reg_conf;
+
+ AddressSpace iommu_as;
+ MemoryRegion iommu;
+
uint64_t ctl;
TyphoonWindow win[4];
} TyphoonPchip;
@@ -209,53 +213,53 @@ static uint64_t pchip_read(void *opaque, hwaddr addr, unsigned size)
switch (addr) {
case 0x0000:
/* WSBA0: Window Space Base Address Register. */
- ret = s->pchip.win[0].base_addr;
+ ret = s->pchip.win[0].wba;
break;
case 0x0040:
/* WSBA1 */
- ret = s->pchip.win[1].base_addr;
+ ret = s->pchip.win[1].wba;
break;
case 0x0080:
/* WSBA2 */
- ret = s->pchip.win[2].base_addr;
+ ret = s->pchip.win[2].wba;
break;
case 0x00c0:
/* WSBA3 */
- ret = s->pchip.win[3].base_addr;
+ ret = s->pchip.win[3].wba;
break;
case 0x0100:
/* WSM0: Window Space Mask Register. */
- ret = s->pchip.win[0].mask;
+ ret = s->pchip.win[0].wsm;
break;
case 0x0140:
/* WSM1 */
- ret = s->pchip.win[1].mask;
+ ret = s->pchip.win[1].wsm;
break;
case 0x0180:
/* WSM2 */
- ret = s->pchip.win[2].mask;
+ ret = s->pchip.win[2].wsm;
break;
case 0x01c0:
/* WSM3 */
- ret = s->pchip.win[3].mask;
+ ret = s->pchip.win[3].wsm;
break;
case 0x0200:
/* TBA0: Translated Base Address Register. */
- ret = (uint64_t)s->pchip.win[0].translated_base_pfn << 10;
+ ret = s->pchip.win[0].tba;
break;
case 0x0240:
/* TBA1 */
- ret = (uint64_t)s->pchip.win[1].translated_base_pfn << 10;
+ ret = s->pchip.win[1].tba;
break;
case 0x0280:
/* TBA2 */
- ret = (uint64_t)s->pchip.win[2].translated_base_pfn << 10;
+ ret = s->pchip.win[2].tba;
break;
case 0x02c0:
/* TBA3 */
- ret = (uint64_t)s->pchip.win[3].translated_base_pfn << 10;
+ ret = s->pchip.win[3].tba;
break;
case 0x0300:
@@ -458,53 +462,53 @@ static void pchip_write(void *opaque, hwaddr addr,
switch (addr) {
case 0x0000:
/* WSBA0: Window Space Base Address Register. */
- s->pchip.win[0].base_addr = val;
+ s->pchip.win[0].wba = val & 0xfff00003u;
break;
case 0x0040:
/* WSBA1 */
- s->pchip.win[1].base_addr = val;
+ s->pchip.win[1].wba = val & 0xfff00003u;
break;
case 0x0080:
/* WSBA2 */
- s->pchip.win[2].base_addr = val;
+ s->pchip.win[2].wba = val & 0xfff00003u;
break;
case 0x00c0:
/* WSBA3 */
- s->pchip.win[3].base_addr = val;
+ s->pchip.win[3].wba = (val & 0x80fff00001ull) | 2;
break;
case 0x0100:
/* WSM0: Window Space Mask Register. */
- s->pchip.win[0].mask = val;
+ s->pchip.win[0].wsm = val & 0xfff00000u;
break;
case 0x0140:
/* WSM1 */
- s->pchip.win[1].mask = val;
+ s->pchip.win[1].wsm = val & 0xfff00000u;
break;
case 0x0180:
/* WSM2 */
- s->pchip.win[2].mask = val;
+ s->pchip.win[2].wsm = val & 0xfff00000u;
break;
case 0x01c0:
/* WSM3 */
- s->pchip.win[3].mask = val;
+ s->pchip.win[3].wsm = val & 0xfff00000u;
break;
case 0x0200:
/* TBA0: Translated Base Address Register. */
- s->pchip.win[0].translated_base_pfn = val >> 10;
+ s->pchip.win[0].tba = val & 0x7fffffc00ull;
break;
case 0x0240:
/* TBA1 */
- s->pchip.win[1].translated_base_pfn = val >> 10;
+ s->pchip.win[1].tba = val & 0x7fffffc00ull;
break;
case 0x0280:
/* TBA2 */
- s->pchip.win[2].translated_base_pfn = val >> 10;
+ s->pchip.win[2].tba = val & 0x7fffffc00ull;
break;
case 0x02c0:
/* TBA3 */
- s->pchip.win[3].translated_base_pfn = val >> 10;
+ s->pchip.win[3].tba = val & 0x7fffffc00ull;
break;
case 0x0300:
@@ -512,7 +516,6 @@ static void pchip_write(void *opaque, hwaddr addr,
oldval = s->pchip.ctl;
oldval &= ~0x00001cff0fc7ffull; /* RW fields */
oldval |= val & 0x00001cff0fc7ffull;
-
s->pchip.ctl = oldval;
break;
@@ -593,6 +596,140 @@ static const MemoryRegionOps pchip_ops = {
},
};
+/* A subroutine of typhoon_translate_iommu that builds an IOMMUTLBEntry
+ using the given translated address and mask. */
+static bool make_iommu_tlbe(hwaddr taddr, hwaddr mask, IOMMUTLBEntry *ret)
+{
+ *ret = (IOMMUTLBEntry) {
+ .target_as = &address_space_memory,
+ .translated_addr = taddr,
+ .addr_mask = mask,
+ .perm = IOMMU_RW,
+ };
+ return true;
+}
+
+/* A subroutine of typhoon_translate_iommu that handles scatter-gather
+ translation, given the address of the PTE. */
+static bool pte_translate(hwaddr pte_addr, IOMMUTLBEntry *ret)
+{
+ uint64_t pte = ldq_phys(pte_addr);
+
+ /* Check valid bit. */
+ if ((pte & 1) == 0) {
+ return false;
+ }
+
+ return make_iommu_tlbe((pte & 0x3ffffe) << 12, 0x1fff, ret);
+}
+
+/* A subroutine of typhoon_translate_iommu that handles one of the
+ four single-address-cycle translation windows. */
+static bool window_translate(TyphoonWindow *win, hwaddr addr,
+ IOMMUTLBEntry *ret)
+{
+ uint32_t wba = win->wba;
+ uint64_t wsm = win->wsm;
+ uint64_t tba = win->tba;
+ uint64_t wsm_ext = wsm | 0xfffff;
+
+ /* Check for window disabled. */
+ if ((wba & 1) == 0) {
+ return false;
+ }
+
+ /* Check for window hit. */
+ if ((addr & ~wsm_ext) != (wba & 0xfff00000u)) {
+ return false;
+ }
+
+ if (wba & 2) {
+ /* Scatter-gather translation. */
+ hwaddr pte_addr;
+
+ /* See table 10-6, Generating PTE address for PCI DMA Address. */
+ pte_addr = tba & ~(wsm >> 10);
+ pte_addr |= (addr & (wsm | 0xfe000)) >> 10;
+ return pte_translate(pte_addr, ret);
+ } else {
+ /* Direct-mapped translation. */
+ return make_iommu_tlbe(tba & ~wsm_ext, wsm_ext, ret);
+ }
+}
+
+/* Handle PCI-to-system address translation. */
+/* TODO: A translation failure here ought to set PCI error codes on the
+ Pchip and generate a machine check interrupt. */
+static IOMMUTLBEntry typhoon_translate_iommu(MemoryRegion *iommu, hwaddr addr)
+{
+ TyphoonPchip *pchip = container_of(iommu, TyphoonPchip, iommu);
+ IOMMUTLBEntry ret;
+ int i;
+
+ if (addr <= 0xffffffffu) {
+ /* Single-address cycle. */
+
+ /* Check for the Window Hole, inhibiting matching. */
+ if ((pchip->ctl & 0x20)
+ && addr >= 0x80000
+ && addr <= 0xfffff) {
+ goto failure;
+ }
+
+ /* Check the first three windows. */
+ for (i = 0; i < 3; ++i) {
+ if (window_translate(&pchip->win[i], addr, &ret)) {
+ goto success;
+ }
+ }
+
+ /* Check the fourth window for DAC disable. */
+ if ((pchip->win[3].wba & 0x80000000000ull) == 0
+ && window_translate(&pchip->win[3], addr, &ret)) {
+ goto success;
+ }
+ } else {
+ /* Double-address cycle. */
+
+ if (addr >= 0x10000000000ull && addr < 0x20000000000ull) {
+ /* Check for the DMA monster window. */
+ if (pchip->ctl & 0x40) {
+ /* See 10.1.4.4; in particular <39:35> is ignored. */
+ make_iommu_tlbe(0, 0x007ffffffffull, &ret);
+ goto success;
+ }
+ }
+
+ if (addr >= 0x80000000000 && addr <= 0xfffffffffff) {
+ /* Check the fourth window for DAC enable and window enable. */
+ if ((pchip->win[3].wba & 0x80000000001ull) == 0x80000000001ull) {
+ uint64_t pte_addr;
+
+ pte_addr = pchip->win[3].tba & 0x7ffc00000ull;
+ pte_addr |= (addr & 0xffffe000u) >> 10;
+ if (pte_translate(pte_addr, &ret)) {
+ goto success;
+ }
+ }
+ }
+ }
+
+ failure:
+ ret = (IOMMUTLBEntry) { .perm = IOMMU_NONE };
+ success:
+ return ret;
+}
+
+static const MemoryRegionIOMMUOps typhoon_iommu_ops = {
+ .translate = typhoon_translate_iommu,
+};
+
+static AddressSpace *typhoon_pci_dma_iommu(PCIBus *bus, void *opaque, int devfn)
+{
+ TyphoonState *s = opaque;
+ return &s->pchip.iommu_as;
+}
+
static void typhoon_set_irq(void *opaque, int irq, int level)
{
TyphoonState *s = opaque;
@@ -688,6 +825,9 @@ PCIBus *typhoon_init(ram_addr_t ram_size, ISABus **isa_bus,
s = TYPHOON_PCI_HOST_BRIDGE(dev);
phb = PCI_HOST_BRIDGE(dev);
+ s->cchip.misc = 0x800000000ull; /* Revision: Typhoon. */
+ s->pchip.win[3].wba = 2; /* Window 3 SG always enabled. */
+
/* Remember the CPUs so that we can deliver interrupts to them. */
for (i = 0; i < 4; i++) {
AlphaCPU *cpu = cpus[i];
@@ -746,6 +886,12 @@ PCIBus *typhoon_init(ram_addr_t ram_size, ISABus **isa_bus,
0, 64, TYPE_PCI_BUS);
phb->bus = b;
+ /* Host memory as seen from the PCI side, via the IOMMU. */
+ memory_region_init_iommu(&s->pchip.iommu, OBJECT(s), &typhoon_iommu_ops,
+ "iommu-typhoon", UINT64_MAX);
+ address_space_init(&s->pchip.iommu_as, &s->pchip.iommu, "pchip0-pci");
+ pci_setup_iommu(b, typhoon_pci_dma_iommu, s);
+
/* Pchip0 PCI special/interrupt acknowledge, 0x801.F800.0000, 64MB. */
memory_region_init_io(&s->pchip.reg_iack, OBJECT(s), &alpha_pci_iack_ops,
b, "pci0-iack", 64*MB);
diff --git a/target-alpha/helper.h b/target-alpha/helper.h
index 0e425cfc08..732b701d53 100644
--- a/target-alpha/helper.h
+++ b/target-alpha/helper.h
@@ -99,6 +99,7 @@ DEF_HELPER_FLAGS_2(ieee_input_cmp, TCG_CALL_NO_WG, void, env, i64)
#if !defined (CONFIG_USER_ONLY)
DEF_HELPER_2(hw_ret, void, env, i64)
+DEF_HELPER_3(call_pal, void, env, i64, i64)
DEF_HELPER_1(ldl_phys, i64, i64)
DEF_HELPER_1(ldq_phys, i64, i64)
@@ -111,6 +112,7 @@ DEF_HELPER_3(stq_c_phys, i64, env, i64, i64)
DEF_HELPER_FLAGS_1(tbia, TCG_CALL_NO_RWG, void, env)
DEF_HELPER_FLAGS_2(tbis, TCG_CALL_NO_RWG, void, env, i64)
+DEF_HELPER_FLAGS_1(tb_flush, TCG_CALL_NO_RWG, void, env)
DEF_HELPER_1(halt, void, i64);
diff --git a/target-alpha/sys_helper.c b/target-alpha/sys_helper.c
index bd94597d36..97cf9ebfc9 100644
--- a/target-alpha/sys_helper.c
+++ b/target-alpha/sys_helper.c
@@ -51,6 +51,17 @@ void helper_hw_ret(CPUAlphaState *env, uint64_t a)
}
}
+void helper_call_pal(CPUAlphaState *env, uint64_t pc, uint64_t entry_ofs)
+{
+ int pal_mode = env->pal_mode;
+ env->exc_addr = pc | pal_mode;
+ env->pc = env->palbr + entry_ofs;
+ if (!pal_mode) {
+ env->pal_mode = 1;
+ swap_shadow_regs(env);
+ }
+}
+
void helper_tbia(CPUAlphaState *env)
{
tlb_flush(env, 1);
@@ -61,6 +72,11 @@ void helper_tbis(CPUAlphaState *env, uint64_t p)
tlb_flush_page(env, p);
}
+void helper_tb_flush(CPUAlphaState *env)
+{
+ tb_flush(env);
+}
+
void helper_halt(uint64_t restart)
{
if (restart) {
@@ -91,4 +107,5 @@ void helper_set_alarm(CPUAlphaState *env, uint64_t expire)
qemu_del_timer(cpu->alarm_timer);
}
}
+
#endif /* CONFIG_USER_ONLY */
diff --git a/target-alpha/translate.c b/target-alpha/translate.c
index 0efd5595e6..309dea6ff0 100644
--- a/target-alpha/translate.c
+++ b/target-alpha/translate.c
@@ -379,13 +379,26 @@ static ExitStatus gen_store_conditional(DisasContext *ctx, int ra, int rb,
#endif
}
-static int use_goto_tb(DisasContext *ctx, uint64_t dest)
+static bool in_superpage(DisasContext *ctx, int64_t addr)
{
- /* Check for the dest on the same page as the start of the TB. We
- also want to suppress goto_tb in the case of single-steping and IO. */
- return (((ctx->tb->pc ^ dest) & TARGET_PAGE_MASK) == 0
- && !ctx->singlestep_enabled
- && !(ctx->tb->cflags & CF_LAST_IO));
+ return ((ctx->tb->flags & TB_FLAGS_USER_MODE) == 0
+ && addr < 0
+ && ((addr >> 41) & 3) == 2
+ && addr >> TARGET_VIRT_ADDR_SPACE_BITS == addr >> 63);
+}
+
+static bool use_goto_tb(DisasContext *ctx, uint64_t dest)
+{
+ /* Suppress goto_tb in the case of single-steping and IO. */
+ if (ctx->singlestep_enabled || (ctx->tb->cflags & CF_LAST_IO)) {
+ return false;
+ }
+ /* If the destination is in the superpage, the page perms can't change. */
+ if (in_superpage(ctx, dest)) {
+ return true;
+ }
+ /* Check for the dest on the same page as the start of the TB. */
+ return ((ctx->tb->pc ^ dest) & TARGET_PAGE_MASK) == 0;
}
static ExitStatus gen_bdirect(DisasContext *ctx, int ra, int32_t disp)
@@ -1521,7 +1534,8 @@ static ExitStatus gen_call_pal(DisasContext *ctx, int palcode)
tcg_gen_mov_i64(cpu_unique, cpu_ir[IR_A0]);
break;
default:
- return gen_excp(ctx, EXCP_CALL_PAL, palcode & 0xbf);
+ palcode &= 0xbf;
+ goto do_call_pal;
}
return NO_EXIT;
}
@@ -1586,13 +1600,42 @@ static ExitStatus gen_call_pal(DisasContext *ctx, int palcode)
break;
default:
- return gen_excp(ctx, EXCP_CALL_PAL, palcode & 0x3f);
+ palcode &= 0x3f;
+ goto do_call_pal;
}
return NO_EXIT;
}
#endif
-
return gen_invalid(ctx);
+
+ do_call_pal:
+#ifdef CONFIG_USER_ONLY
+ return gen_excp(ctx, EXCP_CALL_PAL, palcode);
+#else
+ {
+ TCGv pc = tcg_const_i64(ctx->pc);
+ TCGv entry = tcg_const_i64(palcode & 0x80
+ ? 0x2000 + (palcode - 0x80) * 64
+ : 0x1000 + palcode * 64);
+
+ gen_helper_call_pal(cpu_env, pc, entry);
+
+ tcg_temp_free(entry);
+ tcg_temp_free(pc);
+
+ /* Since the destination is running in PALmode, we don't really
+ need the page permissions check. We'll see the existance of
+ the page when we create the TB, and we'll flush all TBs if
+ we change the PAL base register. */
+ if (!ctx->singlestep_enabled && !(ctx->tb->cflags & CF_LAST_IO)) {
+ tcg_gen_goto_tb(0);
+ tcg_gen_exit_tb((tcg_target_long)ctx->tb);
+ return EXIT_GOTO_TB;
+ }
+
+ return EXIT_PC_UPDATED;
+ }
+#endif
}
#ifndef CONFIG_USER_ONLY
@@ -1708,6 +1751,15 @@ static ExitStatus gen_mtpr(DisasContext *ctx, int rb, int regno)
gen_helper_set_alarm(cpu_env, tmp);
break;
+ case 7:
+ /* PALBR */
+ tcg_gen_st_i64(tmp, cpu_env, offsetof(CPUAlphaState, palbr));
+ /* Changing the PAL base register implies un-chaining all of the TBs
+ that ended with a CALL_PAL. Since the base register usually only
+ changes during boot, flushing everything works well. */
+ gen_helper_tb_flush(cpu_env);
+ return EXIT_PC_STALE;
+
default:
/* The basic registers are data only, and unknown registers
are read-zero, write-ignore. */
@@ -3392,6 +3444,7 @@ static inline void gen_intermediate_code_internal(AlphaCPU *cpu,
CPUAlphaState *env = &cpu->env;
DisasContext ctx, *ctxp = &ctx;
target_ulong pc_start;
+ target_ulong pc_mask;
uint32_t insn;
uint16_t *gen_opc_end;
CPUBreakpoint *bp;
@@ -3421,8 +3474,15 @@ static inline void gen_intermediate_code_internal(AlphaCPU *cpu,
num_insns = 0;
max_insns = tb->cflags & CF_COUNT_MASK;
- if (max_insns == 0)
+ if (max_insns == 0) {
max_insns = CF_COUNT_MASK;
+ }
+
+ if (in_superpage(&ctx, pc_start)) {
+ pc_mask = (1ULL << 41) - 1;
+ } else {
+ pc_mask = ~TARGET_PAGE_MASK;
+ }
gen_tb_start();
do {
@@ -3460,7 +3520,7 @@ static inline void gen_intermediate_code_internal(AlphaCPU *cpu,
/* If we reach a page boundary, are single stepping,
or exhaust instruction count, stop generation. */
if (ret == NO_EXIT
- && ((ctx.pc & (TARGET_PAGE_SIZE - 1)) == 0
+ && ((ctx.pc & pc_mask) == 0
|| tcg_ctx.gen_opc_ptr >= gen_opc_end
|| num_insns >= max_insns
|| singlestep