* GNU General Public License for more details.
*/
+#include "qemu/error-report.h"
+#include "exec/address-spaces.h"
+#include "sysemu/kvm.h"
#include "cpu.h"
/* #define DEBUG_S390 */
#define FS_READ 0x800
#define FS_WRITE 0x400
+static void trigger_access_exception(CPUS390XState *env, uint32_t type,
+ uint32_t ilen, uint64_t tec)
+{
+ S390CPU *cpu = s390_env_get_cpu(env);
+
+ if (kvm_enabled()) {
+ kvm_s390_access_exception(cpu, type, tec);
+ } else {
+ CPUState *cs = CPU(cpu);
+ stq_phys(cs->as, env->psa + offsetof(LowCore, trans_exc_code), tec);
+ trigger_pgm_exception(env, type, ilen);
+ }
+}
+
static void trigger_prot_fault(CPUS390XState *env, target_ulong vaddr,
uint64_t asc, int rw, bool exc)
{
- CPUState *cs = CPU(s390_env_get_cpu(env));
uint64_t tec;
- tec = vaddr | (rw == 1 ? FS_WRITE : FS_READ) | 4 | asc >> 46;
+ tec = vaddr | (rw == MMU_DATA_STORE ? FS_WRITE : FS_READ) | 4 | asc >> 46;
DPRINTF("%s: trans_exc_code=%016" PRIx64 "\n", __func__, tec);
return;
}
- stq_phys(cs->as, env->psa + offsetof(LowCore, trans_exc_code), tec);
- trigger_pgm_exception(env, PGM_PROTECTION, ILEN_LATER_INC);
+ trigger_access_exception(env, PGM_PROTECTION, ILEN_LATER_INC, tec);
}
static void trigger_page_fault(CPUS390XState *env, target_ulong vaddr,
uint32_t type, uint64_t asc, int rw, bool exc)
{
- CPUState *cs = CPU(s390_env_get_cpu(env));
int ilen = ILEN_LATER;
uint64_t tec;
- tec = vaddr | (rw == 1 ? FS_WRITE : FS_READ) | asc >> 46;
+ tec = vaddr | (rw == MMU_DATA_STORE ? FS_WRITE : FS_READ) | asc >> 46;
DPRINTF("%s: vaddr=%016" PRIx64 " bits=%d\n", __func__, vaddr, bits);
}
/* Code accesses have an undefined ilc. */
- if (rw == 2) {
+ if (rw == MMU_INST_FETCH) {
ilen = 2;
}
- stq_phys(cs->as, env->psa + offsetof(LowCore, trans_exc_code), tec);
- trigger_pgm_exception(env, type, ilen);
+ trigger_access_exception(env, type, ilen, tec);
}
/**
/* Decode page table entry (normal 4KB page) */
static int mmu_translate_pte(CPUS390XState *env, target_ulong vaddr,
- uint64_t asc, uint64_t asce,
+ uint64_t asc, uint64_t pt_entry,
target_ulong *raddr, int *flags, int rw, bool exc)
{
- if (asce & _PAGE_INVALID) {
- DPRINTF("%s: PTE=0x%" PRIx64 " invalid\n", __func__, asce);
+ if (pt_entry & _PAGE_INVALID) {
+ DPRINTF("%s: PTE=0x%" PRIx64 " invalid\n", __func__, pt_entry);
trigger_page_fault(env, vaddr, PGM_PAGE_TRANS, asc, rw, exc);
return -1;
}
-
- if (asce & _PAGE_RO) {
+ if (pt_entry & _PAGE_RES0) {
+ trigger_page_fault(env, vaddr, PGM_TRANS_SPEC, asc, rw, exc);
+ return -1;
+ }
+ if (pt_entry & _PAGE_RO) {
*flags &= ~PAGE_WRITE;
}
- *raddr = asce & _ASCE_ORIGIN;
+ *raddr = pt_entry & _ASCE_ORIGIN;
- PTE_DPRINTF("%s: PTE=0x%" PRIx64 "\n", __func__, asce);
+ PTE_DPRINTF("%s: PTE=0x%" PRIx64 "\n", __func__, pt_entry);
return 0;
}
raddr, flags, rw, exc);
}
-static int mmu_translate_asc(CPUS390XState *env, target_ulong vaddr,
- uint64_t asc, target_ulong *raddr, int *flags,
- int rw, bool exc)
+static int mmu_translate_asce(CPUS390XState *env, target_ulong vaddr,
+ uint64_t asc, uint64_t asce, target_ulong *raddr,
+ int *flags, int rw, bool exc)
{
- uint64_t asce = 0;
int level;
int r;
- switch (asc) {
- case PSW_ASC_PRIMARY:
- PTE_DPRINTF("%s: asc=primary\n", __func__);
- asce = env->cregs[1];
- break;
- case PSW_ASC_SECONDARY:
- PTE_DPRINTF("%s: asc=secondary\n", __func__);
- asce = env->cregs[7];
- break;
- case PSW_ASC_HOME:
- PTE_DPRINTF("%s: asc=home\n", __func__);
- asce = env->cregs[13];
- break;
- }
-
if (asce & _ASCE_REAL_SPACE) {
/* direct mapping */
*raddr = vaddr;
r = mmu_translate_region(env, vaddr, asc, asce, level, raddr, flags, rw,
exc);
- if ((rw == 1) && !(*flags & PAGE_WRITE)) {
+ if (rw == MMU_DATA_STORE && !(*flags & PAGE_WRITE)) {
trigger_prot_fault(env, vaddr, asc, rw, exc);
return -1;
}
* @param asc address space control (one of the PSW_ASC_* modes)
* @param raddr the translated address is stored to this pointer
* @param flags the PAGE_READ/WRITE/EXEC flags are stored to this pointer
- * @param exc true = inject a program check if a fault occured
- * @return 0 if the translation was successfull, -1 if a fault occured
+ * @param exc true = inject a program check if a fault occurred
+ * @return 0 if the translation was successful, -1 if a fault occurred
*/
int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc,
target_ulong *raddr, int *flags, bool exc)
switch (asc) {
case PSW_ASC_PRIMARY:
+ PTE_DPRINTF("%s: asc=primary\n", __func__);
+ r = mmu_translate_asce(env, vaddr, asc, env->cregs[1], raddr, flags,
+ rw, exc);
+ break;
case PSW_ASC_HOME:
- r = mmu_translate_asc(env, vaddr, asc, raddr, flags, rw, exc);
+ PTE_DPRINTF("%s: asc=home\n", __func__);
+ r = mmu_translate_asce(env, vaddr, asc, env->cregs[13], raddr, flags,
+ rw, exc);
break;
case PSW_ASC_SECONDARY:
+ PTE_DPRINTF("%s: asc=secondary\n", __func__);
/*
* Instruction: Primary
* Data: Secondary
*/
- if (rw == 2) {
- r = mmu_translate_asc(env, vaddr, PSW_ASC_PRIMARY, raddr, flags,
- rw, exc);
+ if (rw == MMU_INST_FETCH) {
+ r = mmu_translate_asce(env, vaddr, PSW_ASC_PRIMARY, env->cregs[1],
+ raddr, flags, rw, exc);
*flags &= ~(PAGE_READ | PAGE_WRITE);
} else {
- r = mmu_translate_asc(env, vaddr, PSW_ASC_SECONDARY, raddr, flags,
- rw, exc);
+ r = mmu_translate_asce(env, vaddr, PSW_ASC_SECONDARY, env->cregs[7],
+ raddr, flags, rw, exc);
*flags &= ~(PAGE_EXEC);
}
break;
/* Convert real address -> absolute address */
*raddr = mmu_real2abs(env, *raddr);
- if (*raddr <= ram_size) {
+ if (*raddr < ram_size) {
sk = &env->storage_keys[*raddr / TARGET_PAGE_SIZE];
if (*flags & PAGE_READ) {
*sk |= SK_R;
return r;
}
+
+/**
+ * lowprot_enabled: Check whether low-address protection is enabled
+ */
+static bool lowprot_enabled(const CPUS390XState *env)
+{
+ if (!(env->cregs[0] & CR0_LOWPROT)) {
+ return false;
+ }
+ if (!(env->psw.mask & PSW_MASK_DAT)) {
+ return true;
+ }
+
+ /* Check the private-space control bit */
+ switch (env->psw.mask & PSW_MASK_ASC) {
+ case PSW_ASC_PRIMARY:
+ return !(env->cregs[1] & _ASCE_PRIVATE_SPACE);
+ case PSW_ASC_SECONDARY:
+ return !(env->cregs[7] & _ASCE_PRIVATE_SPACE);
+ case PSW_ASC_HOME:
+ return !(env->cregs[13] & _ASCE_PRIVATE_SPACE);
+ default:
+ /* We don't support access register mode */
+ error_report("unsupported addressing mode");
+ exit(1);
+ }
+}
+
+/**
+ * translate_pages: Translate a set of consecutive logical page addresses
+ * to absolute addresses
+ */
+static int translate_pages(S390CPU *cpu, vaddr addr, int nr_pages,
+ target_ulong *pages, bool is_write)
+{
+ bool lowprot = is_write && lowprot_enabled(&cpu->env);
+ uint64_t asc = cpu->env.psw.mask & PSW_MASK_ASC;
+ CPUS390XState *env = &cpu->env;
+ int ret, i, pflags;
+
+ for (i = 0; i < nr_pages; i++) {
+ /* Low-address protection? */
+ if (lowprot && (addr < 512 || (addr >= 4096 && addr < 4096 + 512))) {
+ trigger_access_exception(env, PGM_PROTECTION, ILEN_LATER_INC, 0);
+ return -EACCES;
+ }
+ ret = mmu_translate(env, addr, is_write, asc, &pages[i], &pflags, true);
+ if (ret) {
+ return ret;
+ }
+ if (!address_space_access_valid(&address_space_memory, pages[i],
+ TARGET_PAGE_SIZE, is_write)) {
+ program_interrupt(env, PGM_ADDRESSING, 0);
+ return -EFAULT;
+ }
+ addr += TARGET_PAGE_SIZE;
+ }
+
+ return 0;
+}
+
+/**
+ * s390_cpu_virt_mem_rw:
+ * @laddr: the logical start address
+ * @ar: the access register number
+ * @hostbuf: buffer in host memory. NULL = do only checks w/o copying
+ * @len: length that should be transferred
+ * @is_write: true = write, false = read
+ * Returns: 0 on success, non-zero if an exception occurred
+ *
+ * Copy from/to guest memory using logical addresses. Note that we inject a
+ * program interrupt in case there is an error while accessing the memory.
+ */
+int s390_cpu_virt_mem_rw(S390CPU *cpu, vaddr laddr, uint8_t ar, void *hostbuf,
+ int len, bool is_write)
+{
+ int currlen, nr_pages, i;
+ target_ulong *pages;
+ int ret;
+
+ if (kvm_enabled()) {
+ ret = kvm_s390_mem_op(cpu, laddr, ar, hostbuf, len, is_write);
+ if (ret >= 0) {
+ return ret;
+ }
+ }
+
+ nr_pages = (((laddr & ~TARGET_PAGE_MASK) + len - 1) >> TARGET_PAGE_BITS)
+ + 1;
+ pages = g_malloc(nr_pages * sizeof(*pages));
+
+ ret = translate_pages(cpu, laddr, nr_pages, pages, is_write);
+ if (ret == 0 && hostbuf != NULL) {
+ /* Copy data by stepping through the area page by page */
+ for (i = 0; i < nr_pages; i++) {
+ currlen = MIN(len, TARGET_PAGE_SIZE - (laddr % TARGET_PAGE_SIZE));
+ cpu_physical_memory_rw(pages[i] | (laddr & ~TARGET_PAGE_MASK),
+ hostbuf, currlen, is_write);
+ laddr += currlen;
+ hostbuf += currlen;
+ len -= currlen;
+ }
+ }
+
+ g_free(pages);
+ return ret;
+}