qemu/target-s390x/mmu_helper.c
<<
>>
Prefs
   1/*
   2 * S390x MMU related functions
   3 *
   4 * Copyright (c) 2011 Alexander Graf
   5 * Copyright (c) 2015 Thomas Huth, IBM Corporation
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License as published by
   9 * the Free Software Foundation; either version 2 of the License, or
  10 * (at your option) any later version.
  11 *
  12 * This program is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 * GNU General Public License for more details.
  16 */
  17
  18#include "qemu/osdep.h"
  19#include "qemu/error-report.h"
  20#include "exec/address-spaces.h"
  21#include "cpu.h"
  22#include "sysemu/kvm.h"
  23#include "trace.h"
  24#include "hw/s390x/storage-keys.h"
  25
  26/* #define DEBUG_S390 */
  27/* #define DEBUG_S390_PTE */
  28/* #define DEBUG_S390_STDOUT */
  29
  30#ifdef DEBUG_S390
  31#ifdef DEBUG_S390_STDOUT
  32#define DPRINTF(fmt, ...) \
  33    do { fprintf(stderr, fmt, ## __VA_ARGS__); \
  34         if (qemu_log_separate()) qemu_log(fmt, ##__VA_ARGS__); } while (0)
  35#else
  36#define DPRINTF(fmt, ...) \
  37    do { qemu_log(fmt, ## __VA_ARGS__); } while (0)
  38#endif
  39#else
  40#define DPRINTF(fmt, ...) \
  41    do { } while (0)
  42#endif
  43
  44#ifdef DEBUG_S390_PTE
  45#define PTE_DPRINTF DPRINTF
  46#else
  47#define PTE_DPRINTF(fmt, ...) \
  48    do { } while (0)
  49#endif
  50
  51/* Fetch/store bits in the translation exception code: */
  52#define FS_READ  0x800
  53#define FS_WRITE 0x400
  54
  55static void trigger_access_exception(CPUS390XState *env, uint32_t type,
  56                                     uint32_t ilen, uint64_t tec)
  57{
  58    S390CPU *cpu = s390_env_get_cpu(env);
  59
  60    if (kvm_enabled()) {
  61        kvm_s390_access_exception(cpu, type, tec);
  62    } else {
  63        CPUState *cs = CPU(cpu);
  64        stq_phys(cs->as, env->psa + offsetof(LowCore, trans_exc_code), tec);
  65        trigger_pgm_exception(env, type, ilen);
  66    }
  67}
  68
  69static void trigger_prot_fault(CPUS390XState *env, target_ulong vaddr,
  70                               uint64_t asc, int rw, bool exc)
  71{
  72    uint64_t tec;
  73
  74    tec = vaddr | (rw == MMU_DATA_STORE ? FS_WRITE : FS_READ) | 4 | asc >> 46;
  75
  76    DPRINTF("%s: trans_exc_code=%016" PRIx64 "\n", __func__, tec);
  77
  78    if (!exc) {
  79        return;
  80    }
  81
  82    trigger_access_exception(env, PGM_PROTECTION, ILEN_LATER_INC, tec);
  83}
  84
  85static void trigger_page_fault(CPUS390XState *env, target_ulong vaddr,
  86                               uint32_t type, uint64_t asc, int rw, bool exc)
  87{
  88    int ilen = ILEN_LATER;
  89    uint64_t tec;
  90
  91    tec = vaddr | (rw == MMU_DATA_STORE ? FS_WRITE : FS_READ) | asc >> 46;
  92
  93    DPRINTF("%s: trans_exc_code=%016" PRIx64 "\n", __func__, tec);
  94
  95    if (!exc) {
  96        return;
  97    }
  98
  99    /* Code accesses have an undefined ilc.  */
 100    if (rw == MMU_INST_FETCH) {
 101        ilen = 2;
 102    }
 103
 104    trigger_access_exception(env, type, ilen, tec);
 105}
 106
 107/**
 108 * Translate real address to absolute (= physical)
 109 * address by taking care of the prefix mapping.
 110 */
 111static target_ulong mmu_real2abs(CPUS390XState *env, target_ulong raddr)
 112{
 113    if (raddr < 0x2000) {
 114        return raddr + env->psa;    /* Map the lowcore. */
 115    } else if (raddr >= env->psa && raddr < env->psa + 0x2000) {
 116        return raddr - env->psa;    /* Map the 0 page. */
 117    }
 118    return raddr;
 119}
 120
 121/* Decode page table entry (normal 4KB page) */
 122static int mmu_translate_pte(CPUS390XState *env, target_ulong vaddr,
 123                             uint64_t asc, uint64_t pt_entry,
 124                             target_ulong *raddr, int *flags, int rw, bool exc)
 125{
 126    if (pt_entry & _PAGE_INVALID) {
 127        DPRINTF("%s: PTE=0x%" PRIx64 " invalid\n", __func__, pt_entry);
 128        trigger_page_fault(env, vaddr, PGM_PAGE_TRANS, asc, rw, exc);
 129        return -1;
 130    }
 131    if (pt_entry & _PAGE_RES0) {
 132        trigger_page_fault(env, vaddr, PGM_TRANS_SPEC, asc, rw, exc);
 133        return -1;
 134    }
 135    if (pt_entry & _PAGE_RO) {
 136        *flags &= ~PAGE_WRITE;
 137    }
 138
 139    *raddr = pt_entry & _ASCE_ORIGIN;
 140
 141    PTE_DPRINTF("%s: PTE=0x%" PRIx64 "\n", __func__, pt_entry);
 142
 143    return 0;
 144}
 145
 146#define VADDR_PX    0xff000         /* Page index bits */
 147
 148/* Decode segment table entry */
 149static int mmu_translate_segment(CPUS390XState *env, target_ulong vaddr,
 150                                 uint64_t asc, uint64_t st_entry,
 151                                 target_ulong *raddr, int *flags, int rw,
 152                                 bool exc)
 153{
 154    CPUState *cs = CPU(s390_env_get_cpu(env));
 155    uint64_t origin, offs, pt_entry;
 156
 157    if (st_entry & _SEGMENT_ENTRY_RO) {
 158        *flags &= ~PAGE_WRITE;
 159    }
 160
 161    if ((st_entry & _SEGMENT_ENTRY_FC) && (env->cregs[0] & CR0_EDAT)) {
 162        /* Decode EDAT1 segment frame absolute address (1MB page) */
 163        *raddr = (st_entry & 0xfffffffffff00000ULL) | (vaddr & 0xfffff);
 164        PTE_DPRINTF("%s: SEG=0x%" PRIx64 "\n", __func__, st_entry);
 165        return 0;
 166    }
 167
 168    /* Look up 4KB page entry */
 169    origin = st_entry & _SEGMENT_ENTRY_ORIGIN;
 170    offs  = (vaddr & VADDR_PX) >> 9;
 171    pt_entry = ldq_phys(cs->as, origin + offs);
 172    PTE_DPRINTF("%s: 0x%" PRIx64 " + 0x%" PRIx64 " => 0x%016" PRIx64 "\n",
 173                __func__, origin, offs, pt_entry);
 174    return mmu_translate_pte(env, vaddr, asc, pt_entry, raddr, flags, rw, exc);
 175}
 176
 177/* Decode region table entries */
 178static int mmu_translate_region(CPUS390XState *env, target_ulong vaddr,
 179                                uint64_t asc, uint64_t entry, int level,
 180                                target_ulong *raddr, int *flags, int rw,
 181                                bool exc)
 182{
 183    CPUState *cs = CPU(s390_env_get_cpu(env));
 184    uint64_t origin, offs, new_entry;
 185    const int pchks[4] = {
 186        PGM_SEGMENT_TRANS, PGM_REG_THIRD_TRANS,
 187        PGM_REG_SEC_TRANS, PGM_REG_FIRST_TRANS
 188    };
 189
 190    PTE_DPRINTF("%s: 0x%" PRIx64 "\n", __func__, entry);
 191
 192    origin = entry & _REGION_ENTRY_ORIGIN;
 193    offs = (vaddr >> (17 + 11 * level / 4)) & 0x3ff8;
 194
 195    new_entry = ldq_phys(cs->as, origin + offs);
 196    PTE_DPRINTF("%s: 0x%" PRIx64 " + 0x%" PRIx64 " => 0x%016" PRIx64 "\n",
 197                __func__, origin, offs, new_entry);
 198
 199    if ((new_entry & _REGION_ENTRY_INV) != 0) {
 200        DPRINTF("%s: invalid region\n", __func__);
 201        trigger_page_fault(env, vaddr, pchks[level / 4], asc, rw, exc);
 202        return -1;
 203    }
 204
 205    if ((new_entry & _REGION_ENTRY_TYPE_MASK) != level) {
 206        trigger_page_fault(env, vaddr, PGM_TRANS_SPEC, asc, rw, exc);
 207        return -1;
 208    }
 209
 210    if (level == _ASCE_TYPE_SEGMENT) {
 211        return mmu_translate_segment(env, vaddr, asc, new_entry, raddr, flags,
 212                                     rw, exc);
 213    }
 214
 215    /* Check region table offset and length */
 216    offs = (vaddr >> (28 + 11 * (level - 4) / 4)) & 3;
 217    if (offs < ((new_entry & _REGION_ENTRY_TF) >> 6)
 218        || offs > (new_entry & _REGION_ENTRY_LENGTH)) {
 219        DPRINTF("%s: invalid offset or len (%lx)\n", __func__, new_entry);
 220        trigger_page_fault(env, vaddr, pchks[level / 4 - 1], asc, rw, exc);
 221        return -1;
 222    }
 223
 224    if ((env->cregs[0] & CR0_EDAT) && (new_entry & _REGION_ENTRY_RO)) {
 225        *flags &= ~PAGE_WRITE;
 226    }
 227
 228    /* yet another region */
 229    return mmu_translate_region(env, vaddr, asc, new_entry, level - 4,
 230                                raddr, flags, rw, exc);
 231}
 232
 233static int mmu_translate_asce(CPUS390XState *env, target_ulong vaddr,
 234                              uint64_t asc, uint64_t asce, target_ulong *raddr,
 235                              int *flags, int rw, bool exc)
 236{
 237    int level;
 238    int r;
 239
 240    if (asce & _ASCE_REAL_SPACE) {
 241        /* direct mapping */
 242        *raddr = vaddr;
 243        return 0;
 244    }
 245
 246    level = asce & _ASCE_TYPE_MASK;
 247    switch (level) {
 248    case _ASCE_TYPE_REGION1:
 249        if ((vaddr >> 62) > (asce & _ASCE_TABLE_LENGTH)) {
 250            trigger_page_fault(env, vaddr, PGM_REG_FIRST_TRANS, asc, rw, exc);
 251            return -1;
 252        }
 253        break;
 254    case _ASCE_TYPE_REGION2:
 255        if (vaddr & 0xffe0000000000000ULL) {
 256            DPRINTF("%s: vaddr doesn't fit 0x%16" PRIx64
 257                    " 0xffe0000000000000ULL\n", __func__, vaddr);
 258            trigger_page_fault(env, vaddr, PGM_ASCE_TYPE, asc, rw, exc);
 259            return -1;
 260        }
 261        if ((vaddr >> 51 & 3) > (asce & _ASCE_TABLE_LENGTH)) {
 262            trigger_page_fault(env, vaddr, PGM_REG_SEC_TRANS, asc, rw, exc);
 263            return -1;
 264        }
 265        break;
 266    case _ASCE_TYPE_REGION3:
 267        if (vaddr & 0xfffffc0000000000ULL) {
 268            DPRINTF("%s: vaddr doesn't fit 0x%16" PRIx64
 269                    " 0xfffffc0000000000ULL\n", __func__, vaddr);
 270            trigger_page_fault(env, vaddr, PGM_ASCE_TYPE, asc, rw, exc);
 271            return -1;
 272        }
 273        if ((vaddr >> 40 & 3) > (asce & _ASCE_TABLE_LENGTH)) {
 274            trigger_page_fault(env, vaddr, PGM_REG_THIRD_TRANS, asc, rw, exc);
 275            return -1;
 276        }
 277        break;
 278    case _ASCE_TYPE_SEGMENT:
 279        if (vaddr & 0xffffffff80000000ULL) {
 280            DPRINTF("%s: vaddr doesn't fit 0x%16" PRIx64
 281                    " 0xffffffff80000000ULL\n", __func__, vaddr);
 282            trigger_page_fault(env, vaddr, PGM_ASCE_TYPE, asc, rw, exc);
 283            return -1;
 284        }
 285        if ((vaddr >> 29 & 3) > (asce & _ASCE_TABLE_LENGTH)) {
 286            trigger_page_fault(env, vaddr, PGM_SEGMENT_TRANS, asc, rw, exc);
 287            return -1;
 288        }
 289        break;
 290    }
 291
 292    r = mmu_translate_region(env, vaddr, asc, asce, level, raddr, flags, rw,
 293                             exc);
 294    if (rw == MMU_DATA_STORE && !(*flags & PAGE_WRITE)) {
 295        trigger_prot_fault(env, vaddr, asc, rw, exc);
 296        return -1;
 297    }
 298
 299    return r;
 300}
 301
 302/**
 303 * Translate a virtual (logical) address into a physical (absolute) address.
 304 * @param vaddr  the virtual address
 305 * @param rw     0 = read, 1 = write, 2 = code fetch
 306 * @param asc    address space control (one of the PSW_ASC_* modes)
 307 * @param raddr  the translated address is stored to this pointer
 308 * @param flags  the PAGE_READ/WRITE/EXEC flags are stored to this pointer
 309 * @param exc    true = inject a program check if a fault occurred
 310 * @return       0 if the translation was successful, -1 if a fault occurred
 311 */
 312int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc,
 313                  target_ulong *raddr, int *flags, bool exc)
 314{
 315    static S390SKeysState *ss;
 316    static S390SKeysClass *skeyclass;
 317    int r = -1;
 318    uint8_t key;
 319
 320    if (unlikely(!ss)) {
 321        ss = s390_get_skeys_device();
 322        skeyclass = S390_SKEYS_GET_CLASS(ss);
 323    }
 324
 325    *flags = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
 326    vaddr &= TARGET_PAGE_MASK;
 327
 328    if (!(env->psw.mask & PSW_MASK_DAT)) {
 329        *raddr = vaddr;
 330        r = 0;
 331        goto out;
 332    }
 333
 334    switch (asc) {
 335    case PSW_ASC_PRIMARY:
 336        PTE_DPRINTF("%s: asc=primary\n", __func__);
 337        r = mmu_translate_asce(env, vaddr, asc, env->cregs[1], raddr, flags,
 338                               rw, exc);
 339        break;
 340    case PSW_ASC_HOME:
 341        PTE_DPRINTF("%s: asc=home\n", __func__);
 342        r = mmu_translate_asce(env, vaddr, asc, env->cregs[13], raddr, flags,
 343                               rw, exc);
 344        break;
 345    case PSW_ASC_SECONDARY:
 346        PTE_DPRINTF("%s: asc=secondary\n", __func__);
 347        /*
 348         * Instruction: Primary
 349         * Data: Secondary
 350         */
 351        if (rw == MMU_INST_FETCH) {
 352            r = mmu_translate_asce(env, vaddr, PSW_ASC_PRIMARY, env->cregs[1],
 353                                   raddr, flags, rw, exc);
 354            *flags &= ~(PAGE_READ | PAGE_WRITE);
 355        } else {
 356            r = mmu_translate_asce(env, vaddr, PSW_ASC_SECONDARY, env->cregs[7],
 357                                   raddr, flags, rw, exc);
 358            *flags &= ~(PAGE_EXEC);
 359        }
 360        break;
 361    case PSW_ASC_ACCREG:
 362    default:
 363        hw_error("guest switched to unknown asc mode\n");
 364        break;
 365    }
 366
 367 out:
 368    /* Convert real address -> absolute address */
 369    *raddr = mmu_real2abs(env, *raddr);
 370
 371    if (r == 0 && *raddr < ram_size) {
 372        if (skeyclass->get_skeys(ss, *raddr / TARGET_PAGE_SIZE, 1, &key)) {
 373            trace_get_skeys_nonzero(r);
 374            return 0;
 375        }
 376
 377        if (*flags & PAGE_READ) {
 378            key |= SK_R;
 379        }
 380
 381        if (*flags & PAGE_WRITE) {
 382            key |= SK_C;
 383        }
 384
 385        if (skeyclass->set_skeys(ss, *raddr / TARGET_PAGE_SIZE, 1, &key)) {
 386            trace_set_skeys_nonzero(r);
 387            return 0;
 388        }
 389    }
 390
 391    return r;
 392}
 393
 394/**
 395 * lowprot_enabled: Check whether low-address protection is enabled
 396 */
 397static bool lowprot_enabled(const CPUS390XState *env)
 398{
 399    if (!(env->cregs[0] & CR0_LOWPROT)) {
 400        return false;
 401    }
 402    if (!(env->psw.mask & PSW_MASK_DAT)) {
 403        return true;
 404    }
 405
 406    /* Check the private-space control bit */
 407    switch (env->psw.mask & PSW_MASK_ASC) {
 408    case PSW_ASC_PRIMARY:
 409        return !(env->cregs[1] & _ASCE_PRIVATE_SPACE);
 410    case PSW_ASC_SECONDARY:
 411        return !(env->cregs[7] & _ASCE_PRIVATE_SPACE);
 412    case PSW_ASC_HOME:
 413        return !(env->cregs[13] & _ASCE_PRIVATE_SPACE);
 414    default:
 415        /* We don't support access register mode */
 416        error_report("unsupported addressing mode");
 417        exit(1);
 418    }
 419}
 420
 421/**
 422 * translate_pages: Translate a set of consecutive logical page addresses
 423 * to absolute addresses
 424 */
 425static int translate_pages(S390CPU *cpu, vaddr addr, int nr_pages,
 426                           target_ulong *pages, bool is_write)
 427{
 428    bool lowprot = is_write && lowprot_enabled(&cpu->env);
 429    uint64_t asc = cpu->env.psw.mask & PSW_MASK_ASC;
 430    CPUS390XState *env = &cpu->env;
 431    int ret, i, pflags;
 432
 433    for (i = 0; i < nr_pages; i++) {
 434        /* Low-address protection? */
 435        if (lowprot && (addr < 512 || (addr >= 4096 && addr < 4096 + 512))) {
 436            trigger_access_exception(env, PGM_PROTECTION, ILEN_LATER_INC, 0);
 437            return -EACCES;
 438        }
 439        ret = mmu_translate(env, addr, is_write, asc, &pages[i], &pflags, true);
 440        if (ret) {
 441            return ret;
 442        }
 443        if (!address_space_access_valid(&address_space_memory, pages[i],
 444                                        TARGET_PAGE_SIZE, is_write)) {
 445            program_interrupt(env, PGM_ADDRESSING, 0);
 446            return -EFAULT;
 447        }
 448        addr += TARGET_PAGE_SIZE;
 449    }
 450
 451    return 0;
 452}
 453
 454/**
 455 * s390_cpu_virt_mem_rw:
 456 * @laddr:     the logical start address
 457 * @ar:        the access register number
 458 * @hostbuf:   buffer in host memory. NULL = do only checks w/o copying
 459 * @len:       length that should be transferred
 460 * @is_write:  true = write, false = read
 461 * Returns:    0 on success, non-zero if an exception occurred
 462 *
 463 * Copy from/to guest memory using logical addresses. Note that we inject a
 464 * program interrupt in case there is an error while accessing the memory.
 465 */
 466int s390_cpu_virt_mem_rw(S390CPU *cpu, vaddr laddr, uint8_t ar, void *hostbuf,
 467                         int len, bool is_write)
 468{
 469    int currlen, nr_pages, i;
 470    target_ulong *pages;
 471    int ret;
 472
 473    if (kvm_enabled()) {
 474        ret = kvm_s390_mem_op(cpu, laddr, ar, hostbuf, len, is_write);
 475        if (ret >= 0) {
 476            return ret;
 477        }
 478    }
 479
 480    nr_pages = (((laddr & ~TARGET_PAGE_MASK) + len - 1) >> TARGET_PAGE_BITS)
 481               + 1;
 482    pages = g_malloc(nr_pages * sizeof(*pages));
 483
 484    ret = translate_pages(cpu, laddr, nr_pages, pages, is_write);
 485    if (ret == 0 && hostbuf != NULL) {
 486        /* Copy data by stepping through the area page by page */
 487        for (i = 0; i < nr_pages; i++) {
 488            currlen = MIN(len, TARGET_PAGE_SIZE - (laddr % TARGET_PAGE_SIZE));
 489            cpu_physical_memory_rw(pages[i] | (laddr & ~TARGET_PAGE_MASK),
 490                                   hostbuf, currlen, is_write);
 491            laddr += currlen;
 492            hostbuf += currlen;
 493            len -= currlen;
 494        }
 495    }
 496
 497    g_free(pages);
 498    return ret;
 499}
 500