Commit ba7651fb authored by Max Filippov's avatar Max Filippov
Browse files

target/xtensa: add linux-user support



Import list of syscalls from the kernel source. Conditionalize code/data
that is only used with softmmu. Implement exception handlers. Implement
signal hander (only the core registers for now, no coprocessors or TIE).

Cc: Riku Voipio <riku.voipio@iki.fi>
Cc: Laurent Vivier <laurent@vivier.eu>
Signed-off-by: default avatarMax Filippov <jcmvbkbc@gmail.com>
parent bf9c3a5a
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
# Default configuration for xtensa-linux-user
+1 −0
Original line number Diff line number Diff line
# Default configuration for xtensa-linux-user
+58 −0
Original line number Diff line number Diff line
@@ -1341,6 +1341,64 @@ static inline void init_thread(struct target_pt_regs *regs,

#endif /* TARGET_HPPA */

#ifdef TARGET_XTENSA

#define ELF_START_MMAP 0x20000000

#define ELF_CLASS       ELFCLASS32
#define ELF_ARCH        EM_XTENSA

static inline void init_thread(struct target_pt_regs *regs,
                               struct image_info *infop)
{
    regs->windowbase = 0;
    regs->windowstart = 1;
    regs->areg[1] = infop->start_stack;
    regs->pc = infop->entry;
}

/* See linux kernel: arch/xtensa/include/asm/elf.h.  */
#define ELF_NREG 128
typedef target_elf_greg_t target_elf_gregset_t[ELF_NREG];

enum {
    TARGET_REG_PC,
    TARGET_REG_PS,
    TARGET_REG_LBEG,
    TARGET_REG_LEND,
    TARGET_REG_LCOUNT,
    TARGET_REG_SAR,
    TARGET_REG_WINDOWSTART,
    TARGET_REG_WINDOWBASE,
    TARGET_REG_THREADPTR,
    TARGET_REG_AR0 = 64,
};

static void elf_core_copy_regs(target_elf_gregset_t *regs,
                               const CPUXtensaState *env)
{
    unsigned i;

    (*regs)[TARGET_REG_PC] = tswapreg(env->pc);
    (*regs)[TARGET_REG_PS] = tswapreg(env->sregs[PS] & ~PS_EXCM);
    (*regs)[TARGET_REG_LBEG] = tswapreg(env->sregs[LBEG]);
    (*regs)[TARGET_REG_LEND] = tswapreg(env->sregs[LEND]);
    (*regs)[TARGET_REG_LCOUNT] = tswapreg(env->sregs[LCOUNT]);
    (*regs)[TARGET_REG_SAR] = tswapreg(env->sregs[SAR]);
    (*regs)[TARGET_REG_WINDOWSTART] = tswapreg(env->sregs[WINDOW_START]);
    (*regs)[TARGET_REG_WINDOWBASE] = tswapreg(env->sregs[WINDOW_BASE]);
    (*regs)[TARGET_REG_THREADPTR] = tswapreg(env->uregs[THREADPTR]);
    xtensa_sync_phys_from_window((CPUXtensaState *)env);
    for (i = 0; i < env->config->nareg; ++i) {
        (*regs)[TARGET_REG_AR0 + i] = tswapreg(env->phys_regs[i]);
    }
}

#define USE_ELF_CORE_DUMP
#define ELF_EXEC_PAGESIZE       4096

#endif /* TARGET_XTENSA */

#ifndef ELF_PLATFORM
#define ELF_PLATFORM (NULL)
#endif
+245 −0
Original line number Diff line number Diff line
@@ -3930,6 +3930,242 @@ void cpu_loop(CPUHPPAState *env)

#endif /* TARGET_HPPA */

#ifdef TARGET_XTENSA

static void xtensa_rfw(CPUXtensaState *env)
{
    xtensa_restore_owb(env);
    env->pc = env->sregs[EPC1];
}

static void xtensa_rfwu(CPUXtensaState *env)
{
    env->sregs[WINDOW_START] |= (1 << env->sregs[WINDOW_BASE]);
    xtensa_rfw(env);
}

static void xtensa_rfwo(CPUXtensaState *env)
{
    env->sregs[WINDOW_START] &= ~(1 << env->sregs[WINDOW_BASE]);
    xtensa_rfw(env);
}

static void xtensa_overflow4(CPUXtensaState *env)
{
    put_user_ual(env->regs[0], env->regs[5] - 16);
    put_user_ual(env->regs[1], env->regs[5] - 12);
    put_user_ual(env->regs[2], env->regs[5] -  8);
    put_user_ual(env->regs[3], env->regs[5] -  4);
    xtensa_rfwo(env);
}

static void xtensa_underflow4(CPUXtensaState *env)
{
    get_user_ual(env->regs[0], env->regs[5] - 16);
    get_user_ual(env->regs[1], env->regs[5] - 12);
    get_user_ual(env->regs[2], env->regs[5] -  8);
    get_user_ual(env->regs[3], env->regs[5] -  4);
    xtensa_rfwu(env);
}

static void xtensa_overflow8(CPUXtensaState *env)
{
    put_user_ual(env->regs[0], env->regs[9] - 16);
    get_user_ual(env->regs[0], env->regs[1] - 12);
    put_user_ual(env->regs[1], env->regs[9] - 12);
    put_user_ual(env->regs[2], env->regs[9] -  8);
    put_user_ual(env->regs[3], env->regs[9] -  4);
    put_user_ual(env->regs[4], env->regs[0] - 32);
    put_user_ual(env->regs[5], env->regs[0] - 28);
    put_user_ual(env->regs[6], env->regs[0] - 24);
    put_user_ual(env->regs[7], env->regs[0] - 20);
    xtensa_rfwo(env);
}

static void xtensa_underflow8(CPUXtensaState *env)
{
    get_user_ual(env->regs[0], env->regs[9] - 16);
    get_user_ual(env->regs[1], env->regs[9] - 12);
    get_user_ual(env->regs[2], env->regs[9] -  8);
    get_user_ual(env->regs[7], env->regs[1] - 12);
    get_user_ual(env->regs[3], env->regs[9] -  4);
    get_user_ual(env->regs[4], env->regs[7] - 32);
    get_user_ual(env->regs[5], env->regs[7] - 28);
    get_user_ual(env->regs[6], env->regs[7] - 24);
    get_user_ual(env->regs[7], env->regs[7] - 20);
    xtensa_rfwu(env);
}

static void xtensa_overflow12(CPUXtensaState *env)
{
    put_user_ual(env->regs[0],  env->regs[13] - 16);
    get_user_ual(env->regs[0],  env->regs[1]  - 12);
    put_user_ual(env->regs[1],  env->regs[13] - 12);
    put_user_ual(env->regs[2],  env->regs[13] -  8);
    put_user_ual(env->regs[3],  env->regs[13] -  4);
    put_user_ual(env->regs[4],  env->regs[0]  - 48);
    put_user_ual(env->regs[5],  env->regs[0]  - 44);
    put_user_ual(env->regs[6],  env->regs[0]  - 40);
    put_user_ual(env->regs[7],  env->regs[0]  - 36);
    put_user_ual(env->regs[8],  env->regs[0]  - 32);
    put_user_ual(env->regs[9],  env->regs[0]  - 28);
    put_user_ual(env->regs[10], env->regs[0]  - 24);
    put_user_ual(env->regs[11], env->regs[0]  - 20);
    xtensa_rfwo(env);
}

static void xtensa_underflow12(CPUXtensaState *env)
{
    get_user_ual(env->regs[0],  env->regs[13] - 16);
    get_user_ual(env->regs[1],  env->regs[13] - 12);
    get_user_ual(env->regs[2],  env->regs[13] -  8);
    get_user_ual(env->regs[11], env->regs[1]  - 12);
    get_user_ual(env->regs[3],  env->regs[13] -  4);
    get_user_ual(env->regs[4],  env->regs[11] - 48);
    get_user_ual(env->regs[5],  env->regs[11] - 44);
    get_user_ual(env->regs[6],  env->regs[11] - 40);
    get_user_ual(env->regs[7],  env->regs[11] - 36);
    get_user_ual(env->regs[8],  env->regs[11] - 32);
    get_user_ual(env->regs[9],  env->regs[11] - 28);
    get_user_ual(env->regs[10], env->regs[11] - 24);
    get_user_ual(env->regs[11], env->regs[11] - 20);
    xtensa_rfwu(env);
}

void cpu_loop(CPUXtensaState *env)
{
    CPUState *cs = CPU(xtensa_env_get_cpu(env));
    target_siginfo_t info;
    abi_ulong ret;
    int trapnr;

    while (1) {
        cpu_exec_start(cs);
        trapnr = cpu_exec(cs);
        cpu_exec_end(cs);
        process_queued_cpu_work(cs);

        env->sregs[PS] &= ~PS_EXCM;
        switch (trapnr) {
        case EXCP_INTERRUPT:
            break;

        case EXC_WINDOW_OVERFLOW4:
            xtensa_overflow4(env);
            break;
        case EXC_WINDOW_UNDERFLOW4:
            xtensa_underflow4(env);
            break;
        case EXC_WINDOW_OVERFLOW8:
            xtensa_overflow8(env);
            break;
        case EXC_WINDOW_UNDERFLOW8:
            xtensa_underflow8(env);
            break;
        case EXC_WINDOW_OVERFLOW12:
            xtensa_overflow12(env);
            break;
        case EXC_WINDOW_UNDERFLOW12:
            xtensa_underflow12(env);
            break;

        case EXC_USER:
            switch (env->sregs[EXCCAUSE]) {
            case ILLEGAL_INSTRUCTION_CAUSE:
            case PRIVILEGED_CAUSE:
                info.si_signo = TARGET_SIGILL;
                info.si_errno = 0;
                info.si_code =
                    env->sregs[EXCCAUSE] == ILLEGAL_INSTRUCTION_CAUSE ?
                    TARGET_ILL_ILLOPC : TARGET_ILL_PRVOPC;
                info._sifields._sigfault._addr = env->sregs[EPC1];
                queue_signal(env, info.si_signo, QEMU_SI_FAULT, &info);
                break;

            case SYSCALL_CAUSE:
                env->pc += 3;
                ret = do_syscall(env, env->regs[2],
                                 env->regs[6], env->regs[3],
                                 env->regs[4], env->regs[5],
                                 env->regs[8], env->regs[9], 0, 0);
                switch (ret) {
                default:
                    env->regs[2] = ret;
                    break;

                case -TARGET_ERESTARTSYS:
                case -TARGET_QEMU_ESIGRETURN:
                    break;
                }
                break;

            case ALLOCA_CAUSE:
                env->sregs[PS] = deposit32(env->sregs[PS],
                                           PS_OWB_SHIFT,
                                           PS_OWB_LEN,
                                           env->sregs[WINDOW_BASE]);

                switch (env->regs[0] & 0xc0000000) {
                case 0x00000000:
                case 0x40000000:
                    xtensa_rotate_window(env, -1);
                    xtensa_underflow4(env);
                    break;

                case 0x80000000:
                    xtensa_rotate_window(env, -2);
                    xtensa_underflow8(env);
                    break;

                case 0xc0000000:
                    xtensa_rotate_window(env, -3);
                    xtensa_underflow12(env);
                    break;
                }
                break;

            case INTEGER_DIVIDE_BY_ZERO_CAUSE:
                info.si_signo = TARGET_SIGFPE;
                info.si_errno = 0;
                info.si_code = TARGET_FPE_INTDIV;
                info._sifields._sigfault._addr = env->sregs[EPC1];
                queue_signal(env, info.si_signo, QEMU_SI_FAULT, &info);
                break;

            case LOAD_PROHIBITED_CAUSE:
            case STORE_PROHIBITED_CAUSE:
                info.si_signo = TARGET_SIGSEGV;
                info.si_errno = 0;
                info.si_code = TARGET_SEGV_ACCERR;
                info._sifields._sigfault._addr = env->sregs[EXCVADDR];
                queue_signal(env, info.si_signo, QEMU_SI_FAULT, &info);
                break;

            default:
                fprintf(stderr, "exccause = %d\n", env->sregs[EXCCAUSE]);
                g_assert_not_reached();
            }
            break;
        case EXCP_DEBUG:
            trapnr = gdb_handlesig(cs, TARGET_SIGTRAP);
            if (trapnr) {
                info.si_signo = trapnr;
                info.si_errno = 0;
                info.si_code = TARGET_TRAP_BRKPT;
                queue_signal(env, trapnr, QEMU_SI_FAULT, &info);
            }
            break;
        case EXC_DEBUG:
        default:
            fprintf(stderr, "trapnr = %d\n", trapnr);
            g_assert_not_reached();
        }
        process_pending_signals(env);
    }
}

#endif /* TARGET_XTENSA */

__thread CPUState *thread_cpu;

bool qemu_cpu_is_self(CPUState *cpu)
@@ -4970,6 +5206,15 @@ int main(int argc, char **argv, char **envp)
        env->iaoq_f = regs->iaoq[0];
        env->iaoq_b = regs->iaoq[1];
    }
#elif defined(TARGET_XTENSA)
    {
        int i;
        for (i = 0; i < 16; ++i) {
            env->regs[i] = regs->areg[i];
        }
        env->sregs[WINDOW_START] = regs->windowstart;
        env->pc = regs->pc;
    }
#else
#error unsupported target CPU
#endif
+255 −1
Original line number Diff line number Diff line
@@ -7051,6 +7051,260 @@ long do_rt_sigreturn(CPUArchState *env)
    return -TARGET_QEMU_ESIGRETURN;
}

#elif defined(TARGET_XTENSA)

struct target_sigcontext {
    abi_ulong sc_pc;
    abi_ulong sc_ps;
    abi_ulong sc_lbeg;
    abi_ulong sc_lend;
    abi_ulong sc_lcount;
    abi_ulong sc_sar;
    abi_ulong sc_acclo;
    abi_ulong sc_acchi;
    abi_ulong sc_a[16];
    abi_ulong sc_xtregs;
};

struct target_ucontext {
    abi_ulong tuc_flags;
    abi_ulong tuc_link;
    target_stack_t tuc_stack;
    struct target_sigcontext tuc_mcontext;
    target_sigset_t tuc_sigmask;
};

struct target_rt_sigframe {
    target_siginfo_t info;
    struct target_ucontext uc;
    /* TODO: xtregs */
    uint8_t retcode[6];
    abi_ulong window[4];
};

static abi_ulong get_sigframe(struct target_sigaction *sa,
                              CPUXtensaState *env,
                              unsigned long framesize)
{
    abi_ulong sp = env->regs[1];

    /* This is the X/Open sanctioned signal stack switching.  */
    if ((sa->sa_flags & TARGET_SA_ONSTACK) != 0 && !sas_ss_flags(sp)) {
        sp = target_sigaltstack_used.ss_sp + target_sigaltstack_used.ss_size;
    }
    return (sp - framesize) & -16;
}

static int flush_window_regs(CPUXtensaState *env)
{
    const uint32_t nareg_mask = env->config->nareg - 1;
    uint32_t wb = env->sregs[WINDOW_BASE];
    uint32_t ws = (xtensa_replicate_windowstart(env) >> (wb + 1)) &
        ((1 << env->config->nareg / 4) - 1);
    uint32_t d = ctz32(ws) + 1;
    uint32_t sp;
    abi_long ret = 0;

    wb += d;
    ws >>= d;

    xtensa_sync_phys_from_window(env);
    sp = env->phys_regs[(wb * 4 + 1) & nareg_mask];

    while (ws && ret == 0) {
        int d;
        int i;
        int idx;

        if (ws & 0x1) {
            ws >>= 1;
            d = 1;
        } else if (ws & 0x2) {
            ws >>= 2;
            d = 2;
            for (i = 0; i < 4; ++i) {
                idx = (wb * 4 + 4 + i) & nareg_mask;
                ret |= put_user_ual(env->phys_regs[idx], sp + (i - 12) * 4);
            }
        } else if (ws & 0x4) {
            ws >>= 3;
            d = 3;
            for (i = 0; i < 8; ++i) {
                idx = (wb * 4 + 4 + i) & nareg_mask;
                ret |= put_user_ual(env->phys_regs[idx], sp + (i - 16) * 4);
            }
        } else {
            g_assert_not_reached();
        }
        sp = env->phys_regs[((wb + d) * 4 + 1) & nareg_mask];
        for (i = 0; i < 4; ++i) {
            idx = (wb * 4 + i) & nareg_mask;
            ret |= put_user_ual(env->phys_regs[idx], sp + (i - 4) * 4);
        }
        wb += d;
    }
    return ret == 0;
}

static int setup_sigcontext(struct target_rt_sigframe *frame,
                            CPUXtensaState *env)
{
    struct target_sigcontext *sc = &frame->uc.tuc_mcontext;
    int i;

    __put_user(env->pc, &sc->sc_pc);
    __put_user(env->sregs[PS], &sc->sc_ps);
    __put_user(env->sregs[LBEG], &sc->sc_lbeg);
    __put_user(env->sregs[LEND], &sc->sc_lend);
    __put_user(env->sregs[LCOUNT], &sc->sc_lcount);
    if (!flush_window_regs(env)) {
        return 0;
    }
    for (i = 0; i < 16; ++i) {
        __put_user(env->regs[i], sc->sc_a + i);
    }
    __put_user(0, &sc->sc_xtregs);
    /* TODO: xtregs */
    return 1;
}

static void setup_rt_frame(int sig, struct target_sigaction *ka,
                           target_siginfo_t *info,
                           target_sigset_t *set, CPUXtensaState *env)
{
    abi_ulong frame_addr;
    struct target_rt_sigframe *frame;
    uint32_t ra;
    int i;

    frame_addr = get_sigframe(ka, env, sizeof(*frame));
    trace_user_setup_rt_frame(env, frame_addr);

    if (!lock_user_struct(VERIFY_WRITE, frame, frame_addr, 0)) {
        goto give_sigsegv;
    }

    if (ka->sa_flags & SA_SIGINFO) {
        tswap_siginfo(&frame->info, info);
    }

    __put_user(0, &frame->uc.tuc_flags);
    __put_user(0, &frame->uc.tuc_link);
    __put_user(target_sigaltstack_used.ss_sp,
               &frame->uc.tuc_stack.ss_sp);
    __put_user(sas_ss_flags(env->regs[1]),
               &frame->uc.tuc_stack.ss_flags);
    __put_user(target_sigaltstack_used.ss_size,
               &frame->uc.tuc_stack.ss_size);
    if (!setup_sigcontext(frame, env)) {
        unlock_user_struct(frame, frame_addr, 0);
        goto give_sigsegv;
    }
    for (i = 0; i < TARGET_NSIG_WORDS; ++i) {
        __put_user(set->sig[i], &frame->uc.tuc_sigmask.sig[i]);
    }

    if (ka->sa_flags & TARGET_SA_RESTORER) {
        ra = ka->sa_restorer;
    } else {
        ra = frame_addr + offsetof(struct target_rt_sigframe, retcode);
#ifdef TARGET_WORDS_BIGENDIAN
        /* Generate instruction:  MOVI a2, __NR_rt_sigreturn */
        __put_user(0x22, &frame->retcode[0]);
        __put_user(0x0a, &frame->retcode[1]);
        __put_user(TARGET_NR_rt_sigreturn, &frame->retcode[2]);
        /* Generate instruction:  SYSCALL */
        __put_user(0x00, &frame->retcode[3]);
        __put_user(0x05, &frame->retcode[4]);
        __put_user(0x00, &frame->retcode[5]);
#else
        /* Generate instruction:  MOVI a2, __NR_rt_sigreturn */
        __put_user(0x22, &frame->retcode[0]);
        __put_user(0xa0, &frame->retcode[1]);
        __put_user(TARGET_NR_rt_sigreturn, &frame->retcode[2]);
        /* Generate instruction:  SYSCALL */
        __put_user(0x00, &frame->retcode[3]);
        __put_user(0x50, &frame->retcode[4]);
        __put_user(0x00, &frame->retcode[5]);
#endif
    }
    env->sregs[PS] = PS_UM | (3 << PS_RING_SHIFT);
    if (xtensa_option_enabled(env->config, XTENSA_OPTION_WINDOWED_REGISTER)) {
        env->sregs[PS] |= PS_WOE | (1 << PS_CALLINC_SHIFT);
    }
    memset(env->regs, 0, sizeof(env->regs));
    env->pc = ka->_sa_handler;
    env->regs[1] = frame_addr;
    env->sregs[WINDOW_BASE] = 0;
    env->sregs[WINDOW_START] = 1;

    env->regs[4] = (ra & 0x3fffffff) | 0x40000000;
    env->regs[6] = sig;
    env->regs[7] = frame_addr + offsetof(struct target_rt_sigframe, info);
    env->regs[8] = frame_addr + offsetof(struct target_rt_sigframe, uc);
    unlock_user_struct(frame, frame_addr, 1);
    return;

give_sigsegv:
    force_sigsegv(sig);
    return;
}

static void restore_sigcontext(CPUXtensaState *env,
                               struct target_rt_sigframe *frame)
{
    struct target_sigcontext *sc = &frame->uc.tuc_mcontext;
    uint32_t ps;
    int i;

    __get_user(env->pc, &sc->sc_pc);
    __get_user(ps, &sc->sc_ps);
    __get_user(env->sregs[LBEG], &sc->sc_lbeg);
    __get_user(env->sregs[LEND], &sc->sc_lend);
    __get_user(env->sregs[LCOUNT], &sc->sc_lcount);

    env->sregs[WINDOW_BASE] = 0;
    env->sregs[WINDOW_START] = 1;
    env->sregs[PS] = deposit32(env->sregs[PS],
                               PS_CALLINC_SHIFT,
                               PS_CALLINC_LEN,
                               extract32(ps, PS_CALLINC_SHIFT,
                                         PS_CALLINC_LEN));
    for (i = 0; i < 16; ++i) {
        __get_user(env->regs[i], sc->sc_a + i);
    }
    /* TODO: xtregs */
}

long do_rt_sigreturn(CPUXtensaState *env)
{
    abi_ulong frame_addr = env->regs[1];
    struct target_rt_sigframe *frame;
    sigset_t set;

    trace_user_do_rt_sigreturn(env, frame_addr);
    if (!lock_user_struct(VERIFY_READ, frame, frame_addr, 1)) {
        goto badframe;
    }
    target_to_host_sigset(&set, &frame->uc.tuc_sigmask);
    set_sigmask(&set);

    restore_sigcontext(env, frame);

    if (do_sigaltstack(frame_addr +
                       offsetof(struct target_rt_sigframe, uc.tuc_stack),
                       0, get_sp_from_cpustate(env)) == -TARGET_EFAULT) {
        goto badframe;
    }
    unlock_user_struct(frame, frame_addr, 0);
    return -TARGET_QEMU_ESIGRETURN;

badframe:
    unlock_user_struct(frame, frame_addr, 0);
    force_sig(TARGET_SIGSEGV);
    return -TARGET_QEMU_ESIGRETURN;
}

#else

static void setup_frame(int sig, struct target_sigaction *ka,
@@ -7154,7 +7408,7 @@ static void handle_pending_signal(CPUArchState *cpu_env, int sig,
        || defined(TARGET_OPENRISC) || defined(TARGET_TILEGX) \
        || defined(TARGET_PPC64) || defined(TARGET_HPPA) \
        || defined(TARGET_NIOS2) || defined(TARGET_X86_64) \
        || defined(TARGET_RISCV)
        || defined(TARGET_RISCV) || defined(TARGET_XTENSA)
        /* These targets do not have traditional signals.  */
        setup_rt_frame(sig, sa, &k->info, &target_old_set, cpu_env);
#else
Loading