qemu-e2k/target/i386/tcg/misc_helper.c

214 lines
5.6 KiB
C
Raw Normal View History

/*
* x86 misc helpers
*
* Copyright (c) 2003 Fabrice Bellard
*
* 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.1 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, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "cpu.h"
#include "exec/helper-proto.h"
#include "exec/exec-all.h"
#include "helper-tcg.h"
/*
* NOTE: the translator must set DisasContext.cc_op to CC_OP_EFLAGS
* after generating a call to a helper that uses this.
*/
void cpu_load_eflags(CPUX86State *env, int eflags, int update_mask)
{
CC_SRC = eflags & (CC_O | CC_S | CC_Z | CC_A | CC_P | CC_C);
CC_OP = CC_OP_EFLAGS;
env->df = 1 - (2 * ((eflags >> 10) & 1));
env->eflags = (env->eflags & ~update_mask) |
(eflags & update_mask) | 0x2;
}
void helper_into(CPUX86State *env, int next_eip_addend)
{
int eflags;
eflags = cpu_cc_compute_all(env, CC_OP);
if (eflags & CC_O) {
raise_interrupt(env, EXCP04_INTO, 1, 0, next_eip_addend);
}
}
void helper_cpuid(CPUX86State *env)
{
uint32_t eax, ebx, ecx, edx;
cpu_svm_check_intercept_param(env, SVM_EXIT_CPUID, 0, GETPC());
cpu_x86_cpuid(env, (uint32_t)env->regs[R_EAX], (uint32_t)env->regs[R_ECX],
&eax, &ebx, &ecx, &edx);
env->regs[R_EAX] = eax;
env->regs[R_EBX] = ebx;
env->regs[R_ECX] = ecx;
env->regs[R_EDX] = edx;
}
void helper_lmsw(CPUX86State *env, target_ulong t0)
{
/* only 4 lower bits of CR0 are modified. PE cannot be set to zero
if already set to one. */
t0 = (env->cr[0] & ~0xe) | (t0 & 0xf);
helper_write_crN(env, 0, t0);
}
void helper_invlpg(CPUX86State *env, target_ulong addr)
{
X86CPU *cpu = env_archcpu(env);
cpu_svm_check_intercept_param(env, SVM_EXIT_INVLPG, 0, GETPC());
tlb_flush_page(CPU(cpu), addr);
}
void helper_rdtsc(CPUX86State *env)
{
uint64_t val;
if ((env->cr[4] & CR4_TSD_MASK) && ((env->hflags & HF_CPL_MASK) != 0)) {
raise_exception_ra(env, EXCP0D_GPF, GETPC());
}
cpu_svm_check_intercept_param(env, SVM_EXIT_RDTSC, 0, GETPC());
val = cpu_get_tsc(env) + env->tsc_offset;
env->regs[R_EAX] = (uint32_t)(val);
env->regs[R_EDX] = (uint32_t)(val >> 32);
}
void helper_rdtscp(CPUX86State *env)
{
helper_rdtsc(env);
env->regs[R_ECX] = (uint32_t)(env->tsc_aux);
}
void QEMU_NORETURN helper_rdpmc(CPUX86State *env)
{
if (((env->cr[4] & CR4_PCE_MASK) == 0 ) &&
((env->hflags & HF_CPL_MASK) != 0)) {
raise_exception_ra(env, EXCP0D_GPF, GETPC());
}
cpu_svm_check_intercept_param(env, SVM_EXIT_RDPMC, 0, GETPC());
/* currently unimplemented */
qemu_log_mask(LOG_UNIMP, "x86: unimplemented rdpmc\n");
raise_exception_err(env, EXCP06_ILLOP, 0);
}
static QEMU_NORETURN void do_pause(X86CPU *cpu)
target-i386: yield to another VCPU on PAUSE After commit b1bbfe7 (aio / timers: On timer modification, qemu_notify or aio_notify, 2013-08-21) FreeBSD guests report a huge slowdown. The problem shows up as soon as FreeBSD turns out its periodic (~1 ms) tick, but the timers are only the trigger for a pre-existing problem. Before the offending patch, setting a timer did a timer_settime system call. After, setting the timer exits the event loop (which uses poll) and reenters it with a new deadline. This does not cause any slowdown; the difference is between one system call (timer_settime and a signal delivery (SIGALRM) before the patch, and two system calls afterwards (write to a pipe or eventfd + calling poll again when re-entering the event loop). Unfortunately, the exit/enter causes the main loop to grab the iothread lock, which in turns kicks the VCPU thread out of execution. This causes TCG to execute the next VCPU in its round-robin scheduling of VCPUS. When the second VCPU is mostly unused, FreeBSD runs a "pause" instruction in its idle loop which only burns cycles without any progress. As soon as the timer tick expires, the first VCPU runs the interrupt handler but very soon it sets it again---and QEMU then goes back doing nothing in the second VCPU. The fix is to make the pause instruction do "cpu_loop_exit". Cc: Richard Henderson <rth@twiddle.net> Reported-by: Luigi Rizzo <rizzo@iet.unipi.it> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Reviewed-by: Richard Henderson <rth@twiddle.net> Message-id: 1384948442-24217-1-git-send-email-pbonzini@redhat.com Signed-off-by: Anthony Liguori <aliguori@amazon.com>
2013-11-20 12:54:02 +01:00
{
CPUState *cs = CPU(cpu);
target-i386: yield to another VCPU on PAUSE After commit b1bbfe7 (aio / timers: On timer modification, qemu_notify or aio_notify, 2013-08-21) FreeBSD guests report a huge slowdown. The problem shows up as soon as FreeBSD turns out its periodic (~1 ms) tick, but the timers are only the trigger for a pre-existing problem. Before the offending patch, setting a timer did a timer_settime system call. After, setting the timer exits the event loop (which uses poll) and reenters it with a new deadline. This does not cause any slowdown; the difference is between one system call (timer_settime and a signal delivery (SIGALRM) before the patch, and two system calls afterwards (write to a pipe or eventfd + calling poll again when re-entering the event loop). Unfortunately, the exit/enter causes the main loop to grab the iothread lock, which in turns kicks the VCPU thread out of execution. This causes TCG to execute the next VCPU in its round-robin scheduling of VCPUS. When the second VCPU is mostly unused, FreeBSD runs a "pause" instruction in its idle loop which only burns cycles without any progress. As soon as the timer tick expires, the first VCPU runs the interrupt handler but very soon it sets it again---and QEMU then goes back doing nothing in the second VCPU. The fix is to make the pause instruction do "cpu_loop_exit". Cc: Richard Henderson <rth@twiddle.net> Reported-by: Luigi Rizzo <rizzo@iet.unipi.it> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Reviewed-by: Richard Henderson <rth@twiddle.net> Message-id: 1384948442-24217-1-git-send-email-pbonzini@redhat.com Signed-off-by: Anthony Liguori <aliguori@amazon.com>
2013-11-20 12:54:02 +01:00
/* Just let another CPU run. */
cs->exception_index = EXCP_INTERRUPT;
cpu_loop_exit(cs);
target-i386: yield to another VCPU on PAUSE After commit b1bbfe7 (aio / timers: On timer modification, qemu_notify or aio_notify, 2013-08-21) FreeBSD guests report a huge slowdown. The problem shows up as soon as FreeBSD turns out its periodic (~1 ms) tick, but the timers are only the trigger for a pre-existing problem. Before the offending patch, setting a timer did a timer_settime system call. After, setting the timer exits the event loop (which uses poll) and reenters it with a new deadline. This does not cause any slowdown; the difference is between one system call (timer_settime and a signal delivery (SIGALRM) before the patch, and two system calls afterwards (write to a pipe or eventfd + calling poll again when re-entering the event loop). Unfortunately, the exit/enter causes the main loop to grab the iothread lock, which in turns kicks the VCPU thread out of execution. This causes TCG to execute the next VCPU in its round-robin scheduling of VCPUS. When the second VCPU is mostly unused, FreeBSD runs a "pause" instruction in its idle loop which only burns cycles without any progress. As soon as the timer tick expires, the first VCPU runs the interrupt handler but very soon it sets it again---and QEMU then goes back doing nothing in the second VCPU. The fix is to make the pause instruction do "cpu_loop_exit". Cc: Richard Henderson <rth@twiddle.net> Reported-by: Luigi Rizzo <rizzo@iet.unipi.it> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Reviewed-by: Richard Henderson <rth@twiddle.net> Message-id: 1384948442-24217-1-git-send-email-pbonzini@redhat.com Signed-off-by: Anthony Liguori <aliguori@amazon.com>
2013-11-20 12:54:02 +01:00
}
static QEMU_NORETURN void do_hlt(X86CPU *cpu)
{
CPUState *cs = CPU(cpu);
CPUX86State *env = &cpu->env;
env->hflags &= ~HF_INHIBIT_IRQ_MASK; /* needed if sti is just before */
cs->halted = 1;
cs->exception_index = EXCP_HLT;
cpu_loop_exit(cs);
}
void QEMU_NORETURN helper_hlt(CPUX86State *env, int next_eip_addend)
{
X86CPU *cpu = env_archcpu(env);
cpu_svm_check_intercept_param(env, SVM_EXIT_HLT, 0, GETPC());
env->eip += next_eip_addend;
do_hlt(cpu);
}
void helper_monitor(CPUX86State *env, target_ulong ptr)
{
if ((uint32_t)env->regs[R_ECX] != 0) {
raise_exception_ra(env, EXCP0D_GPF, GETPC());
}
/* XXX: store address? */
cpu_svm_check_intercept_param(env, SVM_EXIT_MONITOR, 0, GETPC());
}
void QEMU_NORETURN helper_mwait(CPUX86State *env, int next_eip_addend)
{
CPUState *cs = env_cpu(env);
X86CPU *cpu = env_archcpu(env);
if ((uint32_t)env->regs[R_ECX] != 0) {
raise_exception_ra(env, EXCP0D_GPF, GETPC());
}
cpu_svm_check_intercept_param(env, SVM_EXIT_MWAIT, 0, GETPC());
env->eip += next_eip_addend;
/* XXX: not complete but not completely erroneous */
if (cs->cpu_index != 0 || CPU_NEXT(cs) != NULL) {
target-i386: yield to another VCPU on PAUSE After commit b1bbfe7 (aio / timers: On timer modification, qemu_notify or aio_notify, 2013-08-21) FreeBSD guests report a huge slowdown. The problem shows up as soon as FreeBSD turns out its periodic (~1 ms) tick, but the timers are only the trigger for a pre-existing problem. Before the offending patch, setting a timer did a timer_settime system call. After, setting the timer exits the event loop (which uses poll) and reenters it with a new deadline. This does not cause any slowdown; the difference is between one system call (timer_settime and a signal delivery (SIGALRM) before the patch, and two system calls afterwards (write to a pipe or eventfd + calling poll again when re-entering the event loop). Unfortunately, the exit/enter causes the main loop to grab the iothread lock, which in turns kicks the VCPU thread out of execution. This causes TCG to execute the next VCPU in its round-robin scheduling of VCPUS. When the second VCPU is mostly unused, FreeBSD runs a "pause" instruction in its idle loop which only burns cycles without any progress. As soon as the timer tick expires, the first VCPU runs the interrupt handler but very soon it sets it again---and QEMU then goes back doing nothing in the second VCPU. The fix is to make the pause instruction do "cpu_loop_exit". Cc: Richard Henderson <rth@twiddle.net> Reported-by: Luigi Rizzo <rizzo@iet.unipi.it> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Reviewed-by: Richard Henderson <rth@twiddle.net> Message-id: 1384948442-24217-1-git-send-email-pbonzini@redhat.com Signed-off-by: Anthony Liguori <aliguori@amazon.com>
2013-11-20 12:54:02 +01:00
do_pause(cpu);
} else {
do_hlt(cpu);
}
}
void QEMU_NORETURN helper_pause(CPUX86State *env, int next_eip_addend)
target-i386: yield to another VCPU on PAUSE After commit b1bbfe7 (aio / timers: On timer modification, qemu_notify or aio_notify, 2013-08-21) FreeBSD guests report a huge slowdown. The problem shows up as soon as FreeBSD turns out its periodic (~1 ms) tick, but the timers are only the trigger for a pre-existing problem. Before the offending patch, setting a timer did a timer_settime system call. After, setting the timer exits the event loop (which uses poll) and reenters it with a new deadline. This does not cause any slowdown; the difference is between one system call (timer_settime and a signal delivery (SIGALRM) before the patch, and two system calls afterwards (write to a pipe or eventfd + calling poll again when re-entering the event loop). Unfortunately, the exit/enter causes the main loop to grab the iothread lock, which in turns kicks the VCPU thread out of execution. This causes TCG to execute the next VCPU in its round-robin scheduling of VCPUS. When the second VCPU is mostly unused, FreeBSD runs a "pause" instruction in its idle loop which only burns cycles without any progress. As soon as the timer tick expires, the first VCPU runs the interrupt handler but very soon it sets it again---and QEMU then goes back doing nothing in the second VCPU. The fix is to make the pause instruction do "cpu_loop_exit". Cc: Richard Henderson <rth@twiddle.net> Reported-by: Luigi Rizzo <rizzo@iet.unipi.it> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Reviewed-by: Richard Henderson <rth@twiddle.net> Message-id: 1384948442-24217-1-git-send-email-pbonzini@redhat.com Signed-off-by: Anthony Liguori <aliguori@amazon.com>
2013-11-20 12:54:02 +01:00
{
X86CPU *cpu = env_archcpu(env);
target-i386: yield to another VCPU on PAUSE After commit b1bbfe7 (aio / timers: On timer modification, qemu_notify or aio_notify, 2013-08-21) FreeBSD guests report a huge slowdown. The problem shows up as soon as FreeBSD turns out its periodic (~1 ms) tick, but the timers are only the trigger for a pre-existing problem. Before the offending patch, setting a timer did a timer_settime system call. After, setting the timer exits the event loop (which uses poll) and reenters it with a new deadline. This does not cause any slowdown; the difference is between one system call (timer_settime and a signal delivery (SIGALRM) before the patch, and two system calls afterwards (write to a pipe or eventfd + calling poll again when re-entering the event loop). Unfortunately, the exit/enter causes the main loop to grab the iothread lock, which in turns kicks the VCPU thread out of execution. This causes TCG to execute the next VCPU in its round-robin scheduling of VCPUS. When the second VCPU is mostly unused, FreeBSD runs a "pause" instruction in its idle loop which only burns cycles without any progress. As soon as the timer tick expires, the first VCPU runs the interrupt handler but very soon it sets it again---and QEMU then goes back doing nothing in the second VCPU. The fix is to make the pause instruction do "cpu_loop_exit". Cc: Richard Henderson <rth@twiddle.net> Reported-by: Luigi Rizzo <rizzo@iet.unipi.it> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Reviewed-by: Richard Henderson <rth@twiddle.net> Message-id: 1384948442-24217-1-git-send-email-pbonzini@redhat.com Signed-off-by: Anthony Liguori <aliguori@amazon.com>
2013-11-20 12:54:02 +01:00
cpu_svm_check_intercept_param(env, SVM_EXIT_PAUSE, 0, GETPC());
target-i386: yield to another VCPU on PAUSE After commit b1bbfe7 (aio / timers: On timer modification, qemu_notify or aio_notify, 2013-08-21) FreeBSD guests report a huge slowdown. The problem shows up as soon as FreeBSD turns out its periodic (~1 ms) tick, but the timers are only the trigger for a pre-existing problem. Before the offending patch, setting a timer did a timer_settime system call. After, setting the timer exits the event loop (which uses poll) and reenters it with a new deadline. This does not cause any slowdown; the difference is between one system call (timer_settime and a signal delivery (SIGALRM) before the patch, and two system calls afterwards (write to a pipe or eventfd + calling poll again when re-entering the event loop). Unfortunately, the exit/enter causes the main loop to grab the iothread lock, which in turns kicks the VCPU thread out of execution. This causes TCG to execute the next VCPU in its round-robin scheduling of VCPUS. When the second VCPU is mostly unused, FreeBSD runs a "pause" instruction in its idle loop which only burns cycles without any progress. As soon as the timer tick expires, the first VCPU runs the interrupt handler but very soon it sets it again---and QEMU then goes back doing nothing in the second VCPU. The fix is to make the pause instruction do "cpu_loop_exit". Cc: Richard Henderson <rth@twiddle.net> Reported-by: Luigi Rizzo <rizzo@iet.unipi.it> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Reviewed-by: Richard Henderson <rth@twiddle.net> Message-id: 1384948442-24217-1-git-send-email-pbonzini@redhat.com Signed-off-by: Anthony Liguori <aliguori@amazon.com>
2013-11-20 12:54:02 +01:00
env->eip += next_eip_addend;
do_pause(cpu);
}
void QEMU_NORETURN helper_debug(CPUX86State *env)
{
CPUState *cs = env_cpu(env);
cs->exception_index = EXCP_DEBUG;
cpu_loop_exit(cs);
}
uint64_t helper_rdpkru(CPUX86State *env, uint32_t ecx)
{
if ((env->cr[4] & CR4_PKE_MASK) == 0) {
raise_exception_err_ra(env, EXCP06_ILLOP, 0, GETPC());
}
if (ecx != 0) {
raise_exception_err_ra(env, EXCP0D_GPF, 0, GETPC());
}
return env->pkru;
}
void helper_wrpkru(CPUX86State *env, uint32_t ecx, uint64_t val)
{
CPUState *cs = env_cpu(env);
if ((env->cr[4] & CR4_PKE_MASK) == 0) {
raise_exception_err_ra(env, EXCP06_ILLOP, 0, GETPC());
}
if (ecx != 0 || (val & 0xFFFFFFFF00000000ull)) {
raise_exception_err_ra(env, EXCP0D_GPF, 0, GETPC());
}
env->pkru = val;
tlb_flush(cs);
}