mmu_helper.c 15.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * S390x MMU related functions
 *
 * Copyright (c) 2011 Alexander Graf
 * Copyright (c) 2015 Thomas Huth, IBM Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 */

P
Peter Maydell 已提交
18
#include "qemu/osdep.h"
19 20
#include "qemu/error-report.h"
#include "exec/address-spaces.h"
21
#include "cpu.h"
22
#include "sysemu/kvm.h"
23 24
#include "trace.h"
#include "hw/s390x/storage-keys.h"
25 26 27 28 29 30 31 32 33

/* #define DEBUG_S390 */
/* #define DEBUG_S390_PTE */
/* #define DEBUG_S390_STDOUT */

#ifdef DEBUG_S390
#ifdef DEBUG_S390_STDOUT
#define DPRINTF(fmt, ...) \
    do { fprintf(stderr, fmt, ## __VA_ARGS__); \
34
         if (qemu_log_separate()) qemu_log(fmt, ##__VA_ARGS__); } while (0)
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
#else
#define DPRINTF(fmt, ...) \
    do { qemu_log(fmt, ## __VA_ARGS__); } while (0)
#endif
#else
#define DPRINTF(fmt, ...) \
    do { } while (0)
#endif

#ifdef DEBUG_S390_PTE
#define PTE_DPRINTF DPRINTF
#else
#define PTE_DPRINTF(fmt, ...) \
    do { } while (0)
#endif

51 52 53
/* Fetch/store bits in the translation exception code: */
#define FS_READ  0x800
#define FS_WRITE 0x400
54

55 56 57 58 59 60 61 62 63 64 65 66 67 68
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);
    }
}

69
static 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 79 80 81
    if (!exc) {
        return;
    }

82
    trigger_access_exception(env, PGM_PROTECTION, ILEN_AUTO, tec);
83 84 85
}

static 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_AUTO;
89 90
    uint64_t tec;

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 96 97 98

    if (!exc) {
        return;
    }

99
    /* Code accesses have an undefined ilc.  */
100
    if (rw == MMU_INST_FETCH) {
101 102 103
        ilen = 2;
    }

104
    trigger_access_exception(env, type, ilen, tec);
105 106 107 108 109 110
}

/**
 * Translate real address to absolute (= physical)
 * address by taking care of the prefix mapping.
 */
111
target_ulong mmu_real2abs(CPUS390XState *env, target_ulong raddr)
112 113 114 115 116 117 118 119 120 121 122
{
    if (raddr < 0x2000) {
        return raddr + env->psa;    /* Map the lowcore. */
    } else if (raddr >= env->psa && raddr < env->psa + 0x2000) {
        return raddr - env->psa;    /* Map the 0 page. */
    }
    return raddr;
}

/* Decode page table entry (normal 4KB page) */
static 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 127
    if (pt_entry & _PAGE_INVALID) {
        DPRINTF("%s: PTE=0x%" PRIx64 " invalid\n", __func__, pt_entry);
128
        trigger_page_fault(env, vaddr, PGM_PAGE_TRANS, asc, rw, exc);
129 130
        return -1;
    }
131 132 133 134
    if (pt_entry & _PAGE_RES0) {
        trigger_page_fault(env, vaddr, PGM_TRANS_SPEC, asc, rw, exc);
        return -1;
    }
135
    if (pt_entry & _PAGE_RO) {
136 137 138
        *flags &= ~PAGE_WRITE;
    }

139
    *raddr = pt_entry & _ASCE_ORIGIN;
140

141
    PTE_DPRINTF("%s: PTE=0x%" PRIx64 "\n", __func__, pt_entry);
142 143 144 145

    return 0;
}

146 147 148
/* Decode segment table entry */
static int mmu_translate_segment(CPUS390XState *env, target_ulong vaddr,
                                 uint64_t asc, uint64_t st_entry,
149 150
                                 target_ulong *raddr, int *flags, int rw,
                                 bool exc)
151
{
152 153
    CPUState *cs = CPU(s390_env_get_cpu(env));
    uint64_t origin, offs, pt_entry;
154

155
    if (st_entry & _SEGMENT_ENTRY_RO) {
156 157 158
        *flags &= ~PAGE_WRITE;
    }

159 160 161 162 163 164
    if ((st_entry & _SEGMENT_ENTRY_FC) && (env->cregs[0] & CR0_EDAT)) {
        /* Decode EDAT1 segment frame absolute address (1MB page) */
        *raddr = (st_entry & 0xfffffffffff00000ULL) | (vaddr & 0xfffff);
        PTE_DPRINTF("%s: SEG=0x%" PRIx64 "\n", __func__, st_entry);
        return 0;
    }
165

166 167 168 169 170 171
    /* Look up 4KB page entry */
    origin = st_entry & _SEGMENT_ENTRY_ORIGIN;
    offs  = (vaddr & VADDR_PX) >> 9;
    pt_entry = ldq_phys(cs->as, origin + offs);
    PTE_DPRINTF("%s: 0x%" PRIx64 " + 0x%" PRIx64 " => 0x%016" PRIx64 "\n",
                __func__, origin, offs, pt_entry);
172
    return mmu_translate_pte(env, vaddr, asc, pt_entry, raddr, flags, rw, exc);
173 174
}

175 176 177
/* Decode region table entries */
static int mmu_translate_region(CPUS390XState *env, target_ulong vaddr,
                                uint64_t asc, uint64_t entry, int level,
178 179
                                target_ulong *raddr, int *flags, int rw,
                                bool exc)
180 181
{
    CPUState *cs = CPU(s390_env_get_cpu(env));
182
    uint64_t origin, offs, new_entry;
183 184 185 186
    const int pchks[4] = {
        PGM_SEGMENT_TRANS, PGM_REG_THIRD_TRANS,
        PGM_REG_SEC_TRANS, PGM_REG_FIRST_TRANS
    };
187 188

    PTE_DPRINTF("%s: 0x%" PRIx64 "\n", __func__, entry);
189

190 191 192 193 194 195
    origin = entry & _REGION_ENTRY_ORIGIN;
    offs = (vaddr >> (17 + 11 * level / 4)) & 0x3ff8;

    new_entry = ldq_phys(cs->as, origin + offs);
    PTE_DPRINTF("%s: 0x%" PRIx64 " + 0x%" PRIx64 " => 0x%016" PRIx64 "\n",
                __func__, origin, offs, new_entry);
196

197
    if ((new_entry & _REGION_ENTRY_INV) != 0) {
198
        DPRINTF("%s: invalid region\n", __func__);
199
        trigger_page_fault(env, vaddr, pchks[level / 4], asc, rw, exc);
200 201 202
        return -1;
    }

203
    if ((new_entry & _REGION_ENTRY_TYPE_MASK) != level) {
204
        trigger_page_fault(env, vaddr, PGM_TRANS_SPEC, asc, rw, exc);
205 206 207 208
        return -1;
    }

    if (level == _ASCE_TYPE_SEGMENT) {
209
        return mmu_translate_segment(env, vaddr, asc, new_entry, raddr, flags,
210
                                     rw, exc);
211
    }
212

213 214 215 216 217
    /* Check region table offset and length */
    offs = (vaddr >> (28 + 11 * (level - 4) / 4)) & 3;
    if (offs < ((new_entry & _REGION_ENTRY_TF) >> 6)
        || offs > (new_entry & _REGION_ENTRY_LENGTH)) {
        DPRINTF("%s: invalid offset or len (%lx)\n", __func__, new_entry);
218
        trigger_page_fault(env, vaddr, pchks[level / 4 - 1], asc, rw, exc);
219 220 221
        return -1;
    }

222 223 224 225
    if ((env->cregs[0] & CR0_EDAT) && (new_entry & _REGION_ENTRY_RO)) {
        *flags &= ~PAGE_WRITE;
    }

226 227
    /* yet another region */
    return mmu_translate_region(env, vaddr, asc, new_entry, level - 4,
228
                                raddr, flags, rw, exc);
229 230
}

231 232 233
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)
234
{
235
    int level;
236 237
    int r;

238 239 240 241 242 243
    if (asce & _ASCE_REAL_SPACE) {
        /* direct mapping */
        *raddr = vaddr;
        return 0;
    }

244 245
    level = asce & _ASCE_TYPE_MASK;
    switch (level) {
246
    case _ASCE_TYPE_REGION1:
247
        if ((vaddr >> 62) > (asce & _ASCE_TABLE_LENGTH)) {
248
            trigger_page_fault(env, vaddr, PGM_REG_FIRST_TRANS, asc, rw, exc);
249 250
            return -1;
        }
251 252 253 254 255
        break;
    case _ASCE_TYPE_REGION2:
        if (vaddr & 0xffe0000000000000ULL) {
            DPRINTF("%s: vaddr doesn't fit 0x%16" PRIx64
                    " 0xffe0000000000000ULL\n", __func__, vaddr);
256
            trigger_page_fault(env, vaddr, PGM_ASCE_TYPE, asc, rw, exc);
257 258
            return -1;
        }
259
        if ((vaddr >> 51 & 3) > (asce & _ASCE_TABLE_LENGTH)) {
260
            trigger_page_fault(env, vaddr, PGM_REG_SEC_TRANS, asc, rw, exc);
261 262
            return -1;
        }
263 264 265 266 267
        break;
    case _ASCE_TYPE_REGION3:
        if (vaddr & 0xfffffc0000000000ULL) {
            DPRINTF("%s: vaddr doesn't fit 0x%16" PRIx64
                    " 0xfffffc0000000000ULL\n", __func__, vaddr);
268
            trigger_page_fault(env, vaddr, PGM_ASCE_TYPE, asc, rw, exc);
269 270
            return -1;
        }
271
        if ((vaddr >> 40 & 3) > (asce & _ASCE_TABLE_LENGTH)) {
272
            trigger_page_fault(env, vaddr, PGM_REG_THIRD_TRANS, asc, rw, exc);
273 274
            return -1;
        }
275 276 277 278 279
        break;
    case _ASCE_TYPE_SEGMENT:
        if (vaddr & 0xffffffff80000000ULL) {
            DPRINTF("%s: vaddr doesn't fit 0x%16" PRIx64
                    " 0xffffffff80000000ULL\n", __func__, vaddr);
280
            trigger_page_fault(env, vaddr, PGM_ASCE_TYPE, asc, rw, exc);
281 282
            return -1;
        }
283
        if ((vaddr >> 29 & 3) > (asce & _ASCE_TABLE_LENGTH)) {
284
            trigger_page_fault(env, vaddr, PGM_SEGMENT_TRANS, asc, rw, exc);
285 286
            return -1;
        }
287 288 289
        break;
    }

290 291
    r = mmu_translate_region(env, vaddr, asc, asce, level, raddr, flags, rw,
                             exc);
292
    if (rw == MMU_DATA_STORE && !(*flags & PAGE_WRITE)) {
293
        trigger_prot_fault(env, vaddr, asc, rw, exc);
294 295 296 297 298 299
        return -1;
    }

    return r;
}

300 301 302 303 304 305 306
/**
 * Translate a virtual (logical) address into a physical (absolute) address.
 * @param vaddr  the virtual address
 * @param rw     0 = read, 1 = write, 2 = code fetch
 * @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
S
Stefan Weil 已提交
307 308
 * @param exc    true = inject a program check if a fault occurred
 * @return       0 if the translation was successful, -1 if a fault occurred
309
 */
310
int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc,
311
                  target_ulong *raddr, int *flags, bool exc)
312
{
313 314
    static S390SKeysState *ss;
    static S390SKeysClass *skeyclass;
315
    int r = -1;
316 317 318 319 320 321
    uint8_t key;

    if (unlikely(!ss)) {
        ss = s390_get_skeys_device();
        skeyclass = S390_SKEYS_GET_CLASS(ss);
    }
322 323 324 325 326 327 328 329 330 331 332 333

    *flags = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
    vaddr &= TARGET_PAGE_MASK;

    if (!(env->psw.mask & PSW_MASK_DAT)) {
        *raddr = vaddr;
        r = 0;
        goto out;
    }

    switch (asc) {
    case PSW_ASC_PRIMARY:
334 335 336 337
        PTE_DPRINTF("%s: asc=primary\n", __func__);
        r = mmu_translate_asce(env, vaddr, asc, env->cregs[1], raddr, flags,
                               rw, exc);
        break;
338
    case PSW_ASC_HOME:
339 340 341
        PTE_DPRINTF("%s: asc=home\n", __func__);
        r = mmu_translate_asce(env, vaddr, asc, env->cregs[13], raddr, flags,
                               rw, exc);
342 343
        break;
    case PSW_ASC_SECONDARY:
344
        PTE_DPRINTF("%s: asc=secondary\n", __func__);
345 346 347 348
        /*
         * Instruction: Primary
         * Data: Secondary
         */
349
        if (rw == MMU_INST_FETCH) {
350 351
            r = mmu_translate_asce(env, vaddr, PSW_ASC_PRIMARY, env->cregs[1],
                                   raddr, flags, rw, exc);
352 353
            *flags &= ~(PAGE_READ | PAGE_WRITE);
        } else {
354 355
            r = mmu_translate_asce(env, vaddr, PSW_ASC_SECONDARY, env->cregs[7],
                                   raddr, flags, rw, exc);
356 357 358 359 360 361 362 363 364 365 366 367 368
            *flags &= ~(PAGE_EXEC);
        }
        break;
    case PSW_ASC_ACCREG:
    default:
        hw_error("guest switched to unknown asc mode\n");
        break;
    }

 out:
    /* Convert real address -> absolute address */
    *raddr = mmu_real2abs(env, *raddr);

369 370 371 372 373 374
    if (r == 0 && *raddr < ram_size) {
        if (skeyclass->get_skeys(ss, *raddr / TARGET_PAGE_SIZE, 1, &key)) {
            trace_get_skeys_nonzero(r);
            return 0;
        }

375
        if (*flags & PAGE_READ) {
376
            key |= SK_R;
377 378 379
        }

        if (*flags & PAGE_WRITE) {
380 381 382 383 384 385
            key |= SK_C;
        }

        if (skeyclass->set_skeys(ss, *raddr / TARGET_PAGE_SIZE, 1, &key)) {
            trace_set_skeys_nonzero(r);
            return 0;
386 387 388 389 390
        }
    }

    return r;
}
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433

/**
 * 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))) {
434
            trigger_access_exception(env, PGM_PROTECTION, ILEN_AUTO, 0);
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
            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
455
 * @ar:        the access register number
456
 * @hostbuf:   buffer in host memory. NULL = do only checks w/o copying
S
Stefan Weil 已提交
457
 * @len:       length that should be transferred
458
 * @is_write:  true = write, false = read
S
Stefan Weil 已提交
459
 * Returns:    0 on success, non-zero if an exception occurred
460 461 462 463
 *
 * 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.
 */
464
int s390_cpu_virt_mem_rw(S390CPU *cpu, vaddr laddr, uint8_t ar, void *hostbuf,
465 466 467 468 469 470
                         int len, bool is_write)
{
    int currlen, nr_pages, i;
    target_ulong *pages;
    int ret;

471
    if (kvm_enabled()) {
472
        ret = kvm_s390_mem_op(cpu, laddr, ar, hostbuf, len, is_write);
473 474 475 476 477
        if (ret >= 0) {
            return ret;
        }
    }

478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
    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;
}