/* * Helpers for HPPA instructions. * * Copyright (c) 2016 Richard Henderson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . */ #include "qemu/osdep.h" #include "qemu/log.h" #include "cpu.h" #include "exec/exec-all.h" #include "exec/helper-proto.h" #include "exec/cpu_ldst.h" #include "qemu/timer.h" #include "trace.h" G_NORETURN void HELPER(excp)(CPUHPPAState *env, int excp) { CPUState *cs = env_cpu(env); cs->exception_index = excp; cpu_loop_exit(cs); } G_NORETURN void hppa_dynamic_excp(CPUHPPAState *env, int excp, uintptr_t ra) { CPUState *cs = env_cpu(env); cs->exception_index = excp; cpu_loop_exit_restore(cs, ra); } static void atomic_store_mask32(CPUHPPAState *env, target_ulong addr, uint32_t val, uint32_t mask, uintptr_t ra) { int mmu_idx = cpu_mmu_index(env_cpu(env), 0); uint32_t old, new, cmp, *haddr; void *vaddr; vaddr = probe_access(env, addr, 3, MMU_DATA_STORE, mmu_idx, ra); if (vaddr == NULL) { cpu_loop_exit_atomic(env_cpu(env), ra); } haddr = (uint32_t *)((uintptr_t)vaddr & -4); mask = addr & 1 ? 0x00ffffffu : 0xffffff00u; old = *haddr; while (1) { new = be32_to_cpu((cpu_to_be32(old) & ~mask) | (val & mask)); cmp = qatomic_cmpxchg(haddr, old, new); if (cmp == old) { return; } old = cmp; } } static void atomic_store_mask64(CPUHPPAState *env, target_ulong addr, uint64_t val, uint64_t mask, int size, uintptr_t ra) { #ifdef CONFIG_ATOMIC64 int mmu_idx = cpu_mmu_index(env_cpu(env), 0); uint64_t old, new, cmp, *haddr; void *vaddr; vaddr = probe_access(env, addr, size, MMU_DATA_STORE, mmu_idx, ra); if (vaddr == NULL) { cpu_loop_exit_atomic(env_cpu(env), ra); } haddr = (uint64_t *)((uintptr_t)vaddr & -8); old = *haddr; while (1) { new = be32_to_cpu((cpu_to_be32(old) & ~mask) | (val & mask)); cmp = qatomic_cmpxchg__nocheck(haddr, old, new); if (cmp == old) { return; } old = cmp; } #else cpu_loop_exit_atomic(env_cpu(env), ra); #endif } static void do_stby_b(CPUHPPAState *env, target_ulong addr, target_ulong val, bool parallel, uintptr_t ra) { switch (addr & 3) { case 3: cpu_stb_data_ra(env, addr, val, ra); break; case 2: cpu_stw_data_ra(env, addr, val, ra); break; case 1: /* The 3 byte store must appear atomic. */ if (parallel) { atomic_store_mask32(env, addr, val, 0x00ffffffu, ra); } else { cpu_stb_data_ra(env, addr, val >> 16, ra); cpu_stw_data_ra(env, addr + 1, val, ra); } break; default: cpu_stl_data_ra(env, addr, val, ra); break; } } static void do_stdby_b(CPUHPPAState *env, target_ulong addr, uint64_t val, bool parallel, uintptr_t ra) { switch (addr & 7) { case 7: cpu_stb_data_ra(env, addr, val, ra); break; case 6: cpu_stw_data_ra(env, addr, val, ra); break; case 5: /* The 3 byte store must appear atomic. */ if (parallel) { atomic_store_mask32(env, addr, val, 0x00ffffffu, ra); } else { cpu_stb_data_ra(env, addr, val >> 16, ra); cpu_stw_data_ra(env, addr + 1, val, ra); } break; case 4: cpu_stl_data_ra(env, addr, val, ra); break; case 3: /* The 5 byte store must appear atomic. */ if (parallel) { atomic_store_mask64(env, addr, val, 0x000000ffffffffffull, 5, ra); } else { cpu_stb_data_ra(env, addr, val >> 32, ra); cpu_stl_data_ra(env, addr + 1, val, ra); } break; case 2: /* The 6 byte store must appear atomic. */ if (parallel) { atomic_store_mask64(env, addr, val, 0x0000ffffffffffffull, 6, ra); } else { cpu_stw_data_ra(env, addr, val >> 32, ra); cpu_stl_data_ra(env, addr + 2, val, ra); } break; case 1: /* The 7 byte store must appear atomic. */ if (parallel) { atomic_store_mask64(env, addr, val, 0x00ffffffffffffffull, 7, ra); } else { cpu_stb_data_ra(env, addr, val >> 48, ra); cpu_stw_data_ra(env, addr + 1, val >> 32, ra); cpu_stl_data_ra(env, addr + 3, val, ra); } break; default: cpu_stq_data_ra(env, addr, val, ra); break; } } void HELPER(stby_b)(CPUHPPAState *env, target_ulong addr, target_ulong val) { do_stby_b(env, addr, val, false, GETPC()); } void HELPER(stby_b_parallel)(CPUHPPAState *env, target_ulong addr, target_ulong val) { do_stby_b(env, addr, val, true, GETPC()); } void HELPER(stdby_b)(CPUHPPAState *env, target_ulong addr, target_ulong val) { do_stdby_b(env, addr, val, false, GETPC()); } void HELPER(stdby_b_parallel)(CPUHPPAState *env, target_ulong addr, target_ulong val) { do_stdby_b(env, addr, val, true, GETPC()); } static void do_stby_e(CPUHPPAState *env, target_ulong addr, target_ulong val, bool parallel, uintptr_t ra) { switch (addr & 3) { case 3: /* The 3 byte store must appear atomic. */ if (parallel) { atomic_store_mask32(env, addr - 3, val, 0xffffff00u, ra); } else { cpu_stw_data_ra(env, addr - 3, val >> 16, ra); cpu_stb_data_ra(env, addr - 1, val >> 8, ra); } break; case 2: cpu_stw_data_ra(env, addr - 2, val >> 16, ra); break; case 1: cpu_stb_data_ra(env, addr - 1, val >> 24, ra); break; default: /* Nothing is stored, but protection is checked and the cacheline is marked dirty. */ probe_write(env, addr, 0, cpu_mmu_index(env_cpu(env), 0), ra); break; } } static void do_stdby_e(CPUHPPAState *env, target_ulong addr, uint64_t val, bool parallel, uintptr_t ra) { switch (addr & 7) { case 7: /* The 7 byte store must appear atomic. */ if (parallel) { atomic_store_mask64(env, addr - 7, val, 0xffffffffffffff00ull, 7, ra); } else { cpu_stl_data_ra(env, addr - 7, val >> 32, ra); cpu_stw_data_ra(env, addr - 3, val >> 16, ra); cpu_stb_data_ra(env, addr - 1, val >> 8, ra); } break; case 6: /* The 6 byte store must appear atomic. */ if (parallel) { atomic_store_mask64(env, addr - 6, val, 0xffffffffffff0000ull, 6, ra); } else { cpu_stl_data_ra(env, addr - 6, val >> 32, ra); cpu_stw_data_ra(env, addr - 2, val >> 16, ra); } break; case 5: /* The 5 byte store must appear atomic. */ if (parallel) { atomic_store_mask64(env, addr - 5, val, 0xffffffffff000000ull, 5, ra); } else { cpu_stl_data_ra(env, addr - 5, val >> 32, ra); cpu_stb_data_ra(env, addr - 1, val >> 24, ra); } break; case 4: cpu_stl_data_ra(env, addr - 4, val >> 32, ra); break; case 3: /* The 3 byte store must appear atomic. */ if (parallel) { atomic_store_mask32(env, addr - 3, val >> 32, 0xffffff00u, ra); } else { cpu_stw_data_ra(env, addr - 3, val >> 48, ra); cpu_stb_data_ra(env, addr - 1, val >> 40, ra); } break; case 2: cpu_stw_data_ra(env, addr - 2, val >> 48, ra); break; case 1: cpu_stb_data_ra(env, addr - 1, val >> 56, ra); break; default: /* Nothing is stored, but protection is checked and the cacheline is marked dirty. */ probe_write(env, addr, 0, cpu_mmu_index(env_cpu(env), 0), ra); break; } } void HELPER(stby_e)(CPUHPPAState *env, target_ulong addr, target_ulong val) { do_stby_e(env, addr, val, false, GETPC()); } void HELPER(stby_e_parallel)(CPUHPPAState *env, target_ulong addr, target_ulong val) { do_stby_e(env, addr, val, true, GETPC()); } void HELPER(stdby_e)(CPUHPPAState *env, target_ulong addr, target_ulong val) { do_stdby_e(env, addr, val, false, GETPC()); } void HELPER(stdby_e_parallel)(CPUHPPAState *env, target_ulong addr, target_ulong val) { do_stdby_e(env, addr, val, true, GETPC()); } void HELPER(ldc_check)(target_ulong addr) { if (unlikely(addr & 0xf)) { qemu_log_mask(LOG_GUEST_ERROR, "Undefined ldc to unaligned address mod 16: " TARGET_FMT_lx "\n", addr); } } target_ulong HELPER(probe)(CPUHPPAState *env, target_ulong addr, uint32_t level, uint32_t want) { #ifdef CONFIG_USER_ONLY return page_check_range(addr, 1, want); #else int prot, excp, mmu_idx; hwaddr phys; trace_hppa_tlb_probe(addr, level, want); /* Fail if the requested privilege level is higher than current. */ if (level < (env->iaoq_f & 3)) { return 0; } mmu_idx = PRIV_P_TO_MMU_IDX(level, env->psw & PSW_P); excp = hppa_get_physical_address(env, addr, mmu_idx, 0, 0, &phys, &prot); if (excp >= 0) { cpu_restore_state(env_cpu(env), GETPC()); hppa_set_ior_and_isr(env, addr, MMU_IDX_MMU_DISABLED(mmu_idx)); if (excp == EXCP_DTLB_MISS) { excp = EXCP_NA_DTLB_MISS; } helper_excp(env, excp); } return (want & prot) != 0; #endif } target_ulong HELPER(read_interval_timer)(void) { #ifdef CONFIG_USER_ONLY /* In user-mode, QEMU_CLOCK_VIRTUAL doesn't exist. Just pass through the host cpu clock ticks. */ return cpu_get_host_ticks(); #else /* In system mode we have access to a decent high-resolution clock. In order to make OS-level time accounting work with the cr16, present it with a well-timed clock fixed at 250MHz. */ return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) >> 2; #endif } uint64_t HELPER(hadd_ss)(uint64_t r1, uint64_t r2) { uint64_t ret = 0; for (int i = 0; i < 64; i += 16) { int f1 = sextract64(r1, i, 16); int f2 = sextract64(r2, i, 16); int fr = f1 + f2; fr = MIN(fr, INT16_MAX); fr = MAX(fr, INT16_MIN); ret = deposit64(ret, i, 16, fr); } return ret; } uint64_t HELPER(hadd_us)(uint64_t r1, uint64_t r2) { uint64_t ret = 0; for (int i = 0; i < 64; i += 16) { int f1 = extract64(r1, i, 16); int f2 = sextract64(r2, i, 16); int fr = f1 + f2; fr = MIN(fr, UINT16_MAX); fr = MAX(fr, 0); ret = deposit64(ret, i, 16, fr); } return ret; } uint64_t HELPER(havg)(uint64_t r1, uint64_t r2) { uint64_t ret = 0; for (int i = 0; i < 64; i += 16) { int f1 = extract64(r1, i, 16); int f2 = extract64(r2, i, 16); int fr = f1 + f2; ret = deposit64(ret, i, 16, (fr >> 1) | (fr & 1)); } return ret; } uint64_t HELPER(hsub_ss)(uint64_t r1, uint64_t r2) { uint64_t ret = 0; for (int i = 0; i < 64; i += 16) { int f1 = sextract64(r1, i, 16); int f2 = sextract64(r2, i, 16); int fr = f1 - f2; fr = MIN(fr, INT16_MAX); fr = MAX(fr, INT16_MIN); ret = deposit64(ret, i, 16, fr); } return ret; } uint64_t HELPER(hsub_us)(uint64_t r1, uint64_t r2) { uint64_t ret = 0; for (int i = 0; i < 64; i += 16) { int f1 = extract64(r1, i, 16); int f2 = sextract64(r2, i, 16); int fr = f1 - f2; fr = MIN(fr, UINT16_MAX); fr = MAX(fr, 0); ret = deposit64(ret, i, 16, fr); } return ret; } uint64_t HELPER(hshladd)(uint64_t r1, uint64_t r2, uint32_t sh) { uint64_t ret = 0; for (int i = 0; i < 64; i += 16) { int f1 = sextract64(r1, i, 16); int f2 = sextract64(r2, i, 16); int fr = (f1 << sh) + f2; fr = MIN(fr, INT16_MAX); fr = MAX(fr, INT16_MIN); ret = deposit64(ret, i, 16, fr); } return ret; } uint64_t HELPER(hshradd)(uint64_t r1, uint64_t r2, uint32_t sh) { uint64_t ret = 0; for (int i = 0; i < 64; i += 16) { int f1 = sextract64(r1, i, 16); int f2 = sextract64(r2, i, 16); int fr = (f1 >> sh) + f2; fr = MIN(fr, INT16_MAX); fr = MAX(fr, INT16_MIN); ret = deposit64(ret, i, 16, fr); } return ret; }