mmu_helper.c 16.2 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 "internal.h"
23
#include "kvm_s390x.h"
24
#include "sysemu/kvm.h"
25 26
#include "trace.h"
#include "hw/s390x/storage-keys.h"
27 28 29 30 31 32 33 34 35

/* #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__); \
36
         if (qemu_log_separate()) qemu_log(fmt, ##__VA_ARGS__); } while (0)
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
#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

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

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

71
static void trigger_prot_fault(CPUS390XState *env, target_ulong vaddr,
72
                               uint64_t asc, int rw, bool exc)
73
{
74
    uint64_t tec;
75

76
    tec = vaddr | (rw == MMU_DATA_STORE ? FS_WRITE : FS_READ) | 4 | asc >> 46;
77 78

    DPRINTF("%s: trans_exc_code=%016" PRIx64 "\n", __func__, tec);
79

80 81 82 83
    if (!exc) {
        return;
    }

84
    trigger_access_exception(env, PGM_PROTECTION, ILEN_AUTO, tec);
85 86 87
}

static void trigger_page_fault(CPUS390XState *env, target_ulong vaddr,
88
                               uint32_t type, uint64_t asc, int rw, bool exc)
89
{
90
    int ilen = ILEN_AUTO;
91 92
    uint64_t tec;

93
    tec = vaddr | (rw == MMU_DATA_STORE ? FS_WRITE : FS_READ) | asc >> 46;
94

95
    DPRINTF("%s: trans_exc_code=%016" PRIx64 "\n", __func__, tec);
96 97 98 99 100

    if (!exc) {
        return;
    }

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

106
    trigger_access_exception(env, type, ilen, tec);
107 108 109 110 111 112
}

/**
 * Translate real address to absolute (= physical)
 * address by taking care of the prefix mapping.
 */
113
target_ulong mmu_real2abs(CPUS390XState *env, target_ulong raddr)
114 115 116 117 118 119 120 121 122 123 124
{
    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,
125
                             uint64_t asc, uint64_t pt_entry,
126
                             target_ulong *raddr, int *flags, int rw, bool exc)
127
{
128 129
    if (pt_entry & _PAGE_INVALID) {
        DPRINTF("%s: PTE=0x%" PRIx64 " invalid\n", __func__, pt_entry);
130
        trigger_page_fault(env, vaddr, PGM_PAGE_TRANS, asc, rw, exc);
131 132
        return -1;
    }
133 134 135 136
    if (pt_entry & _PAGE_RES0) {
        trigger_page_fault(env, vaddr, PGM_TRANS_SPEC, asc, rw, exc);
        return -1;
    }
137
    if (pt_entry & _PAGE_RO) {
138 139 140
        *flags &= ~PAGE_WRITE;
    }

141
    *raddr = pt_entry & _ASCE_ORIGIN;
142

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

    return 0;
}

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

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

161 162 163 164 165 166
    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;
    }
167

168 169 170 171 172 173
    /* 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);
174
    return mmu_translate_pte(env, vaddr, asc, pt_entry, raddr, flags, rw, exc);
175 176
}

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

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

192 193 194 195 196 197
    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);
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 203 204
        return -1;
    }

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

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

215 216 217 218 219
    /* 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);
220
        trigger_page_fault(env, vaddr, pchks[level / 4 - 1], asc, rw, exc);
221 222 223
        return -1;
    }

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

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

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

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

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

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

    return r;
}

302 303 304 305 306 307 308
/**
 * 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 已提交
309 310
 * @param exc    true = inject a program check if a fault occurred
 * @return       0 if the translation was successful, -1 if a fault occurred
311
 */
312
int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc,
313
                  target_ulong *raddr, int *flags, bool exc)
314
{
315 316
    static S390SKeysState *ss;
    static S390SKeysClass *skeyclass;
317
    int r = -1;
318 319 320 321 322 323
    uint8_t key;

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

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

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

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

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

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

    return r;
}
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 434 435

/**
 * 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))) {
436
            trigger_access_exception(env, PGM_PROTECTION, ILEN_AUTO, 0);
437 438 439 440 441 442 443 444
            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)) {
445
            program_interrupt(env, PGM_ADDRESSING, ILEN_AUTO);
446 447 448 449 450 451 452 453 454 455 456
            return -EFAULT;
        }
        addr += TARGET_PAGE_SIZE;
    }

    return 0;
}

/**
 * s390_cpu_virt_mem_rw:
 * @laddr:     the logical start address
457
 * @ar:        the access register number
458
 * @hostbuf:   buffer in host memory. NULL = do only checks w/o copying
S
Stefan Weil 已提交
459
 * @len:       length that should be transferred
460
 * @is_write:  true = write, false = read
S
Stefan Weil 已提交
461
 * Returns:    0 on success, non-zero if an exception occurred
462 463 464 465
 *
 * 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.
 */
466
int s390_cpu_virt_mem_rw(S390CPU *cpu, vaddr laddr, uint8_t ar, void *hostbuf,
467 468 469 470 471 472
                         int len, bool is_write)
{
    int currlen, nr_pages, i;
    target_ulong *pages;
    int ret;

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

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
    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;
}
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518

/**
 * Translate a real address into a physical (absolute) address.
 * @param raddr  the real address
 * @param rw     0 = read, 1 = write, 2 = code fetch
 * @param addr   the translated address is stored to this pointer
 * @param flags  the PAGE_READ/WRITE/EXEC flags are stored to this pointer
 * @return       0 if the translation was successful, < 0 if a fault occurred
 */
int mmu_translate_real(CPUS390XState *env, target_ulong raddr, int rw,
                       target_ulong *addr, int *flags)
{
    /* TODO: low address protection once we flush the tlb on cr changes */
    *flags = PAGE_READ | PAGE_WRITE;
    *addr = mmu_real2abs(env, raddr);

    /* TODO: storage key handling */
    return 0;
}