/*
 *  Alpha emulation - PALcode emulation for qemu.
 *
 *  Copyright (c) 2007 Jocelyn Mayer
 *
 * 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 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA  02110-1301 USA
 */

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#include "qemu.h"
#include "cpu.h"
#include "exec-all.h"

#if !defined (CONFIG_USER_ONLY)
/* Shared handlers */
static void pal_reset (CPUState *env);
/* Console handlers */
static void pal_console_call (CPUState *env, uint32_t palcode);
/* OpenVMS handlers */
static void pal_openvms_call (CPUState *env, uint32_t palcode);
/* UNIX / Linux handlers */
static void pal_unix_call (CPUState *env, uint32_t palcode);

pal_handler_t pal_handlers[] = {
    /* Console handler */
    {
        .reset = &pal_reset,
        .call_pal = &pal_console_call,
    },
    /* OpenVMS handler */
    {
        .reset = &pal_reset,
        .call_pal = &pal_openvms_call,
    },
    /* UNIX / Linux handler */
    {
        .reset = &pal_reset,
        .call_pal = &pal_unix_call,
    },
};

#if 0
/* One must explicitly check that the TB is valid and the FOE bit is reset */
static void update_itb (void)
{
    /* This writes into a temp register, not the actual one */
    mtpr(TB_TAG);
    mtpr(TB_CTL);
    /* This commits the TB update */
    mtpr(ITB_PTE);
}

static void update_dtb (void);
{
    mtpr(TB_CTL);
    /* This write into a temp register, not the actual one */
    mtpr(TB_TAG);
    /* This commits the TB update */
    mtpr(DTB_PTE);
}
#endif

static void pal_reset (CPUState *env)
{
}

static void do_swappal (CPUState *env, uint64_t palid)
{
    pal_handler_t *pal_handler;
    int status;

    status = 0;
    switch (palid) {
    case 0 ... 2:
        pal_handler = &pal_handlers[palid];
        env->pal_handler = pal_handler;
        env->ipr[IPR_PAL_BASE] = -1ULL;
        (*pal_handler->reset)(env);
        break;
    case 3 ... 255:
        /* Unknown identifier */
        env->ir[0] = 1;
        return;
    default:
        /* We were given the entry point address */
        env->pal_handler = NULL;
        env->ipr[IPR_PAL_BASE] = palid;
        env->pc = env->ipr[IPR_PAL_BASE];
        cpu_loop_exit();
    }
}

static void pal_console_call (CPUState *env, uint32_t palcode)
{
    uint64_t palid;

    if (palcode < 0x00000080) {
        /* Privileged palcodes */
        if (!(env->ps >> 3)) {
            /* TODO: generate privilege exception */
        }
    }
    switch (palcode) {
    case 0x00000000:
        /* HALT */
        /* REQUIRED */
        break;
    case 0x00000001:
        /* CFLUSH */
        break;
    case 0x00000002:
        /* DRAINA */
        /* REQUIRED */
        /* Implemented as no-op */
        break;
    case 0x00000009:
        /* CSERVE */
        /* REQUIRED */
        break;
    case 0x0000000A:
        /* SWPPAL */
        /* REQUIRED */
        palid = env->ir[16];
        do_swappal(env, palid);
        break;
    case 0x00000080:
        /* BPT */
        /* REQUIRED */
        break;
    case 0x00000081:
        /* BUGCHK */
        /* REQUIRED */
        break;
    case 0x00000086:
        /* IMB */
        /* REQUIRED */
        /* Implemented as no-op */
        break;
    case 0x0000009E:
        /* RDUNIQUE */
        /* REQUIRED */
        break;
    case 0x0000009F:
        /* WRUNIQUE */
        /* REQUIRED */
        break;
    case 0x000000AA:
        /* GENTRAP */
        /* REQUIRED */
        break;
    default:
        break;
    }
}

static void pal_openvms_call (CPUState *env, uint32_t palcode)
{
    uint64_t palid, val, oldval;

    if (palcode < 0x00000080) {
        /* Privileged palcodes */
        if (!(env->ps >> 3)) {
            /* TODO: generate privilege exception */
        }
    }
    switch (palcode) {
    case 0x00000000:
        /* HALT */
        /* REQUIRED */
        break;
    case 0x00000001:
        /* CFLUSH */
        break;
    case 0x00000002:
        /* DRAINA */
        /* REQUIRED */
        /* Implemented as no-op */
        break;
    case 0x00000003:
        /* LDQP */
        break;
    case 0x00000004:
        /* STQP */
        break;
    case 0x00000005:
        /* SWPCTX */
        break;
    case 0x00000006:
        /* MFPR_ASN */
        if (cpu_alpha_mfpr(env, IPR_ASN, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000007:
        /* MTPR_ASTEN */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_ASTEN, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000008:
        /* MTPR_ASTSR */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_ASTSR, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000009:
        /* CSERVE */
        /* REQUIRED */
        break;
    case 0x0000000A:
        /* SWPPAL */
        /* REQUIRED */
        palid = env->ir[16];
        do_swappal(env, palid);
        break;
    case 0x0000000B:
        /* MFPR_FEN */
        if (cpu_alpha_mfpr(env, IPR_FEN, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x0000000C:
        /* MTPR_FEN */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_FEN, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x0000000D:
        /* MTPR_IPIR */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_IPIR, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x0000000E:
        /* MFPR_IPL */
        if (cpu_alpha_mfpr(env, IPR_IPL, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x0000000F:
        /* MTPR_IPL */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_IPL, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000010:
        /* MFPR_MCES */
        if (cpu_alpha_mfpr(env, IPR_MCES, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000011:
        /* MTPR_MCES */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_MCES, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000012:
        /* MFPR_PCBB */
        if (cpu_alpha_mfpr(env, IPR_PCBB, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000013:
        /* MFPR_PRBR */
        if (cpu_alpha_mfpr(env, IPR_PRBR, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000014:
        /* MTPR_PRBR */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_PRBR, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000015:
        /* MFPR_PTBR */
        if (cpu_alpha_mfpr(env, IPR_PTBR, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000016:
        /* MFPR_SCBB */
        if (cpu_alpha_mfpr(env, IPR_SCBB, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000017:
        /* MTPR_SCBB */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_SCBB, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000018:
        /* MTPR_SIRR */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_SIRR, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000019:
        /* MFPR_SISR */
        if (cpu_alpha_mfpr(env, IPR_SISR, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x0000001A:
        /* MFPR_TBCHK */
        if (cpu_alpha_mfpr(env, IPR_TBCHK, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x0000001B:
        /* MTPR_TBIA */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_TBIA, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x0000001C:
        /* MTPR_TBIAP */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_TBIAP, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x0000001D:
        /* MTPR_TBIS */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_TBIS, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x0000001E:
        /* MFPR_ESP */
        if (cpu_alpha_mfpr(env, IPR_ESP, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x0000001F:
        /* MTPR_ESP */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_ESP, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000020:
        /* MFPR_SSP */
        if (cpu_alpha_mfpr(env, IPR_SSP, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000021:
        /* MTPR_SSP */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_SSP, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000022:
        /* MFPR_USP */
        if (cpu_alpha_mfpr(env, IPR_USP, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000023:
        /* MTPR_USP */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_USP, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000024:
        /* MTPR_TBISD */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_TBISD, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000025:
        /* MTPR_TBISI */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_TBISI, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000026:
        /* MFPR_ASTEN */
        if (cpu_alpha_mfpr(env, IPR_ASTEN, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000027:
        /* MFPR_ASTSR */
        if (cpu_alpha_mfpr(env, IPR_ASTSR, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000029:
        /* MFPR_VPTB */
        if (cpu_alpha_mfpr(env, IPR_VPTB, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x0000002A:
        /* MTPR_VPTB */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_VPTB, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x0000002B:
        /* MTPR_PERFMON */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_PERFMON, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x0000002E:
        /* MTPR_DATFX */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_DATFX, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x0000003E:
        /* WTINT */
        break;
    case 0x0000003F:
        /* MFPR_WHAMI */
        if (cpu_alpha_mfpr(env, IPR_WHAMI, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000080:
        /* BPT */
        /* REQUIRED */
        break;
    case 0x00000081:
        /* BUGCHK */
        /* REQUIRED */
        break;
    case 0x00000082:
        /* CHME */
        break;
    case 0x00000083:
        /* CHMK */
        break;
    case 0x00000084:
        /* CHMS */
        break;
    case 0x00000085:
        /* CHMU */
        break;
    case 0x00000086:
        /* IMB */
        /* REQUIRED */
        /* Implemented as no-op */
        break;
    case 0x00000087:
        /* INSQHIL */
        break;
    case 0x00000088:
        /* INSQTIL */
        break;
    case 0x00000089:
        /* INSQHIQ */
        break;
    case 0x0000008A:
        /* INSQTIQ */
        break;
    case 0x0000008B:
        /* INSQUEL */
        break;
    case 0x0000008C:
        /* INSQUEQ */
        break;
    case 0x0000008D:
        /* INSQUEL/D */
        break;
    case 0x0000008E:
        /* INSQUEQ/D */
        break;
    case 0x0000008F:
        /* PROBER */
        break;
    case 0x00000090:
        /* PROBEW */
        break;
    case 0x00000091:
        /* RD_PS */
        break;
    case 0x00000092:
        /* REI */
        break;
    case 0x00000093:
        /* REMQHIL */
        break;
    case 0x00000094:
        /* REMQTIL */
        break;
    case 0x00000095:
        /* REMQHIQ */
        break;
    case 0x00000096:
        /* REMQTIQ */
        break;
    case 0x00000097:
        /* REMQUEL */
        break;
    case 0x00000098:
        /* REMQUEQ */
        break;
    case 0x00000099:
        /* REMQUEL/D */
        break;
    case 0x0000009A:
        /* REMQUEQ/D */
        break;
    case 0x0000009B:
        /* SWASTEN */
        break;
    case 0x0000009C:
        /* WR_PS_SW */
        break;
    case 0x0000009D:
        /* RSCC */
        break;
    case 0x0000009E:
        /* READ_UNQ */
        /* REQUIRED */
        break;
    case 0x0000009F:
        /* WRITE_UNQ */
        /* REQUIRED */
        break;
    case 0x000000A0:
        /* AMOVRR */
        break;
    case 0x000000A1:
        /* AMOVRM */
        break;
    case 0x000000A2:
        /* INSQHILR */
        break;
    case 0x000000A3:
        /* INSQTILR */
        break;
    case 0x000000A4:
        /* INSQHIQR */
        break;
    case 0x000000A5:
        /* INSQTIQR */
        break;
    case 0x000000A6:
        /* REMQHILR */
        break;
    case 0x000000A7:
        /* REMQTILR */
        break;
    case 0x000000A8:
        /* REMQHIQR */
        break;
    case 0x000000A9:
        /* REMQTIQR */
        break;
    case 0x000000AA:
        /* GENTRAP */
        /* REQUIRED */
        break;
    case 0x000000AE:
        /* CLRFEN */
        break;
    default:
        break;
    }
}

static void pal_unix_call (CPUState *env, uint32_t palcode)
{
    uint64_t palid, val, oldval;

    if (palcode < 0x00000080) {
        /* Privileged palcodes */
        if (!(env->ps >> 3)) {
            /* TODO: generate privilege exception */
        }
    }
    switch (palcode) {
    case 0x00000000:
        /* HALT */
        /* REQUIRED */
        break;
    case 0x00000001:
        /* CFLUSH */
        break;
    case 0x00000002:
        /* DRAINA */
        /* REQUIRED */
        /* Implemented as no-op */
        break;
    case 0x00000009:
        /* CSERVE */
        /* REQUIRED */
        break;
    case 0x0000000A:
        /* SWPPAL */
        /* REQUIRED */
        palid = env->ir[16];
        do_swappal(env, palid);
        break;
    case 0x0000000D:
        /* WRIPIR */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_IPIR, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000010:
        /* RDMCES */
        if (cpu_alpha_mfpr(env, IPR_MCES, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000011:
        /* WRMCES */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_MCES, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x0000002B:
        /* WRFEN */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_PERFMON, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x0000002D:
        /* WRVPTPTR */
        break;
    case 0x00000030:
        /* SWPCTX */
        break;
    case 0x00000031:
        /* WRVAL */
        break;
    case 0x00000032:
        /* RDVAL */
        break;
    case 0x00000033:
        /* TBI */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_TBIS, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000034:
        /* WRENT */
        break;
    case 0x00000035:
        /* SWPIPL */
        break;
    case 0x00000036:
        /* RDPS */
        break;
    case 0x00000037:
        /* WRKGP */
        break;
    case 0x00000038:
        /* WRUSP */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_USP, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x00000039:
        /* WRPERFMON */
        val = env->ir[16];
        if (cpu_alpha_mtpr(env, IPR_PERFMON, val, &oldval) == 1)
            env->ir[0] = val;
        break;
    case 0x0000003A:
        /* RDUSP */
        if (cpu_alpha_mfpr(env, IPR_USP, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x0000003C:
        /* WHAMI */
        if (cpu_alpha_mfpr(env, IPR_WHAMI, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x0000003D:
        /* RETSYS */
        break;
    case 0x0000003E:
        /* WTINT */
        break;
    case 0x0000003F:
        /* RTI */
        if (cpu_alpha_mfpr(env, IPR_WHAMI, &val) == 0)
            env->ir[0] = val;
        break;
    case 0x00000080:
        /* BPT */
        /* REQUIRED */
        break;
    case 0x00000081:
        /* BUGCHK */
        /* REQUIRED */
        break;
    case 0x00000083:
        /* CALLSYS */
        break;
    case 0x00000086:
        /* IMB */
        /* REQUIRED */
        /* Implemented as no-op */
        break;
    case 0x00000092:
        /* URTI */
        break;
    case 0x0000009E:
        /* RDUNIQUE */
        /* REQUIRED */
        break;
    case 0x0000009F:
        /* WRUNIQUE */
        /* REQUIRED */
        break;
    case 0x000000AA:
        /* GENTRAP */
        /* REQUIRED */
        break;
    case 0x000000AE:
        /* CLRFEN */
        break;
    default:
        break;
    }
}

void call_pal (CPUState *env)
{
    pal_handler_t *pal_handler = env->pal_handler;

    switch (env->exception_index) {
    case EXCP_RESET:
        (*pal_handler->reset)(env);
        break;
    case EXCP_MCHK:
        (*pal_handler->machine_check)(env);
        break;
    case EXCP_ARITH:
        (*pal_handler->arithmetic)(env);
        break;
    case EXCP_INTERRUPT:
        (*pal_handler->interrupt)(env);
        break;
    case EXCP_DFAULT:
        (*pal_handler->dfault)(env);
        break;
    case EXCP_DTB_MISS_PAL:
        (*pal_handler->dtb_miss_pal)(env);
        break;
    case EXCP_DTB_MISS_NATIVE:
        (*pal_handler->dtb_miss_native)(env);
        break;
    case EXCP_UNALIGN:
        (*pal_handler->unalign)(env);
        break;
    case EXCP_ITB_MISS:
        (*pal_handler->itb_miss)(env);
        break;
    case EXCP_ITB_ACV:
        (*pal_handler->itb_acv)(env);
        break;
    case EXCP_OPCDEC:
        (*pal_handler->opcdec)(env);
        break;
    case EXCP_FEN:
        (*pal_handler->fen)(env);
        break;
    default:
        if (env->exception_index >= EXCP_CALL_PAL &&
            env->exception_index < EXCP_CALL_PALP) {
            /* Unprivileged PAL call */
            (*pal_handler->call_pal)
                (env, (env->exception_index - EXCP_CALL_PAL) >> 6);
        } else if (env->exception_index >= EXCP_CALL_PALP &&
                   env->exception_index < EXCP_CALL_PALE) {
            /* Privileged PAL call */
            (*pal_handler->call_pal)
                (env, ((env->exception_index - EXCP_CALL_PALP) >> 6) + 0x80);
        } else {
            /* Should never happen */
        }
        break;
    }
    env->ipr[IPR_EXC_ADDR] &= ~1;
}

void pal_init (CPUState *env)
{
    do_swappal(env, 0);
}

#if 0
static uint64_t get_ptebase (CPUState *env, uint64_t vaddr)
{
    uint64_t virbnd, ptbr;

    if ((env->features & FEATURE_VIRBND)) {
        cpu_alpha_mfpr(env, IPR_VIRBND, &virbnd);
        if (vaddr >= virbnd)
            cpu_alpha_mfpr(env, IPR_SYSPTBR, &ptbr);
        else
            cpu_alpha_mfpr(env, IPR_PTBR, &ptbr);
    } else {
        cpu_alpha_mfpr(env, IPR_PTBR, &ptbr);
    }

    return ptbr;
}

static int get_page_bits (CPUState *env)
{
    /* XXX */
    return 13;
}

static int get_pte (uint64_t *pfnp, int *zbitsp, int *protp,
                    uint64_t ptebase, int page_bits, uint64_t level,
                    int mmu_idx, int rw)
{
    uint64_t pteaddr, pte, pfn;
    uint8_t gh;
    int ure, uwe, kre, kwe, foE, foR, foW, v, ret, ar, is_user;

    /* XXX: TOFIX */
    is_user = mmu_idx == MMU_USER_IDX;
    pteaddr = (ptebase << page_bits) + (8 * level);
    pte = ldq_raw(pteaddr);
    /* Decode all interresting PTE fields */
    pfn = pte >> 32;
    uwe = (pte >> 13) & 1;
    kwe = (pte >> 12) & 1;
    ure = (pte >> 9) & 1;
    kre = (pte >> 8) & 1;
    gh = (pte >> 5) & 3;
    foE = (pte >> 3) & 1;
    foW = (pte >> 2) & 1;
    foR = (pte >> 1) & 1;
    v = pte & 1;
    ret = 0;
    if (!v)
        ret = 0x1;
    /* Check access rights */
    ar = 0;
    if (is_user) {
        if (ure)
            ar |= PAGE_READ;
        if (uwe)
            ar |= PAGE_WRITE;
        if (rw == 1 && !uwe)
            ret |= 0x2;
        if (rw != 1 && !ure)
            ret |= 0x2;
    } else {
        if (kre)
            ar |= PAGE_READ;
        if (kwe)
            ar |= PAGE_WRITE;
        if (rw == 1 && !kwe)
            ret |= 0x2;
        if (rw != 1 && !kre)
            ret |= 0x2;
    }
    if (rw == 0 && foR)
        ret |= 0x4;
    if (rw == 2 && foE)
        ret |= 0x8;
    if (rw == 1 && foW)
        ret |= 0xC;
    *pfnp = pfn;
    if (zbitsp != NULL)
        *zbitsp = page_bits + (3 * gh);
    if (protp != NULL)
        *protp = ar;

    return ret;
}

static int paddr_from_pte (uint64_t *paddr, int *zbitsp, int *prot,
                           uint64_t ptebase, int page_bits,
                           uint64_t vaddr, int mmu_idx, int rw)
{
    uint64_t pfn, page_mask, lvl_mask, level1, level2, level3;
    int lvl_bits, ret;

    page_mask = (1ULL << page_bits) - 1ULL;
    lvl_bits = page_bits - 3;
    lvl_mask = (1ULL << lvl_bits) - 1ULL;
    level3 = (vaddr >> page_bits) & lvl_mask;
    level2 = (vaddr >> (page_bits + lvl_bits)) & lvl_mask;
    level1 = (vaddr >> (page_bits + (2 * lvl_bits))) & lvl_mask;
    /* Level 1 PTE */
    ret = get_pte(&pfn, NULL, NULL, ptebase, page_bits, level1, 0, 0);
    switch (ret) {
    case 3:
        /* Access violation */
        return 2;
    case 2:
        /* translation not valid */
        return 1;
    default:
        /* OK */
        break;
    }
    /* Level 2 PTE */
    ret = get_pte(&pfn, NULL, NULL, pfn, page_bits, level2, 0, 0);
    switch (ret) {
    case 3:
        /* Access violation */
        return 2;
    case 2:
        /* translation not valid */
        return 1;
    default:
        /* OK */
        break;
    }
    /* Level 3 PTE */
    ret = get_pte(&pfn, zbitsp, prot, pfn, page_bits, level3, mmu_idx, rw);
    if (ret & 0x1) {
        /* Translation not valid */
        ret = 1;
    } else if (ret & 2) {
        /* Access violation */
        ret = 2;
    } else {
        switch (ret & 0xC) {
        case 0:
            /* OK */
            ret = 0;
            break;
        case 0x4:
            /* Fault on read */
            ret = 3;
            break;
        case 0x8:
            /* Fault on execute */
            ret = 4;
            break;
        case 0xC:
            /* Fault on write */
            ret = 5;
            break;
        }
    }
    *paddr = (pfn << page_bits) | (vaddr & page_mask);

    return 0;
}

static int virtual_to_physical (CPUState *env, uint64_t *physp,
                                int *zbitsp, int *protp,
                                uint64_t virtual, int mmu_idx, int rw)
{
    uint64_t sva, ptebase;
    int seg, page_bits, ret;

    sva = ((int64_t)(virtual << (64 - VA_BITS))) >> (64 - VA_BITS);
    if (sva != virtual)
        seg = -1;
    else
        seg = sva >> (VA_BITS - 2);
    virtual &= ~(0xFFFFFC0000000000ULL << (VA_BITS - 43));
    ptebase = get_ptebase(env, virtual);
    page_bits = get_page_bits(env);
    ret = 0;
    switch (seg) {
    case 0:
        /* seg1: 3 levels of PTE */
        ret = paddr_from_pte(physp, zbitsp, protp, ptebase, page_bits,
                             virtual, mmu_idx, rw);
        break;
    case 1:
        /* seg1: 2 levels of PTE */
        ret = paddr_from_pte(physp, zbitsp, protp, ptebase, page_bits,
                             virtual, mmu_idx, rw);
        break;
    case 2:
        /* kernel segment */
        if (mmu_idx != 0) {
            ret = 2;
        } else {
            *physp = virtual;
        }
        break;
    case 3:
        /* seg1: TB mapped */
        ret = paddr_from_pte(physp, zbitsp, protp, ptebase, page_bits,
                             virtual, mmu_idx, rw);
        break;
    default:
        ret = 1;
        break;
    }

    return ret;
}

/* XXX: code provision */
int cpu_ppc_handle_mmu_fault (CPUState *env, uint32_t address, int rw,
                              int mmu_idx, int is_softmmu)
{
    uint64_t physical, page_size, end;
    int prot, zbits, ret;

#if defined(CONFIG_USER_ONLY)
        ret = 2;
#else
        ret = virtual_to_physical(env, &physical, &zbits, &prot,
                                  address, mmu_idx, rw);
#endif
    switch (ret) {
    case 0:
        /* No fault */
        page_size = 1ULL << zbits;
        address &= ~(page_size - 1);
        for (end = physical + page_size; physical < end; physical += 0x1000) {
            ret = tlb_set_page(env, address, physical, prot,
                               mmu_idx, is_softmmu);
            address += 0x1000;
        }
        break;
#if 0
    case 1:
        env->exception_index = EXCP_DFAULT;
        env->ipr[IPR_EXC_ADDR] = address;
        ret = 1;
        break;
    case 2:
        env->exception_index = EXCP_ACCESS_VIOLATION;
        env->ipr[IPR_EXC_ADDR] = address;
        ret = 1;
        break;
    case 3:
        env->exception_index = EXCP_FAULT_ON_READ;
        env->ipr[IPR_EXC_ADDR] = address;
        ret = 1;
        break;
    case 4:
        env->exception_index = EXCP_FAULT_ON_EXECUTE;
        env->ipr[IPR_EXC_ADDR] = address;
        ret = 1;
    case 5:
        env->exception_index = EXCP_FAULT_ON_WRITE;
        env->ipr[IPR_EXC_ADDR] = address;
        ret = 1;
#endif
    default:
        /* Should never happen */
        env->exception_index = EXCP_MCHK;
        env->ipr[IPR_EXC_ADDR] = address;
        ret = 1;
        break;
    }

    return ret;
}
#endif

#else /* !defined (CONFIG_USER_ONLY) */
void pal_init (CPUState *env)
{
}

void call_pal (CPUState *env, int palcode)
{
    target_long ret;

    qemu_log("%s: palcode %02x\n", __func__, palcode);
    switch (palcode) {
    case 0x83:
        /* CALLSYS */
        qemu_log("CALLSYS n " TARGET_FMT_ld "\n", env->ir[0]);
        ret = do_syscall(env, env->ir[IR_V0], env->ir[IR_A0], env->ir[IR_A1],
                         env->ir[IR_A2], env->ir[IR_A3], env->ir[IR_A4],
                         env->ir[IR_A5]);
        if (ret >= 0) {
            env->ir[IR_A3] = 0;
            env->ir[IR_V0] = ret;
        } else {
            env->ir[IR_A3] = 1;
            env->ir[IR_V0] = -ret;
        }
        break;
    case 0x9E:
        /* RDUNIQUE */
        env->ir[IR_V0] = env->unique;
        qemu_log("RDUNIQUE: " TARGET_FMT_lx "\n", env->unique);
        break;
    case 0x9F:
        /* WRUNIQUE */
        env->unique = env->ir[IR_A0];
        qemu_log("WRUNIQUE: " TARGET_FMT_lx "\n", env->unique);
        break;
    default:
        qemu_log("%s: unhandled palcode %02x\n",
                    __func__, palcode);
        exit(1);
    }
}
#endif
