Skip to content

Commit

Permalink
target/xtensa: refactor CCOUNT/CCOMPARE
Browse files Browse the repository at this point in the history
Xtensa cores may have a register (CCOUNT) that counts core clock cycles.
It may also have a number of registers (CCOMPAREx); when CCOUNT value
passes the value of CCOMPAREx, timer interrupt x is raised.

Currently xtensa target counts a number of completed instructions and
assumes that for CCOUNT one instruction takes one cycle to complete.
It calls helper function to update CCOUNT register at every TB end and
raise timer interrupts. This scheme works very predictably and doesn't
have noticeable performance impact, but it is hard to use with multiple
synchronized processors, especially with coming MTTCG.

Derive CCOUNT from the virtual simulation time, QEMU_CLOCK_VIRTUAL.
Use native QEMU timers for CCOMPARE timers, one timer for each register.

Signed-off-by: Max Filippov <jcmvbkbc@gmail.com>
  • Loading branch information
jcmvbkbc committed Jan 15, 2017
1 parent bd527a8 commit 59a71f7
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 107 deletions.
75 changes: 14 additions & 61 deletions hw/xtensa/pic_cpu.c
Expand Up @@ -31,40 +31,13 @@
#include "qemu/log.h"
#include "qemu/timer.h"

void xtensa_advance_ccount(CPUXtensaState *env, uint32_t d)
{
uint32_t old_ccount = env->sregs[CCOUNT] + 1;

env->sregs[CCOUNT] += d;

if (xtensa_option_enabled(env->config, XTENSA_OPTION_TIMER_INTERRUPT)) {
int i;
for (i = 0; i < env->config->nccompare; ++i) {
if (env->sregs[CCOMPARE + i] - old_ccount < d) {
xtensa_timer_irq(env, i, 1);
}
}
}
}

void check_interrupts(CPUXtensaState *env)
{
CPUState *cs = CPU(xtensa_env_get_cpu(env));
int minlevel = xtensa_get_cintlevel(env);
uint32_t int_set_enabled = env->sregs[INTSET] & env->sregs[INTENABLE];
int level;

/* If the CPU is halted advance CCOUNT according to the QEMU_CLOCK_VIRTUAL time
* elapsed since the moment when it was advanced last time.
*/
if (cs->halted) {
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);

xtensa_advance_ccount(env,
muldiv64(now - env->halt_clock,
env->config->clock_freq_khz, 1000000));
env->halt_clock = now;
}
for (level = env->config->nlevel; level > minlevel; --level) {
if (env->config->level_mask[level] & int_set_enabled) {
env->pending_irq_level = level;
Expand Down Expand Up @@ -109,49 +82,29 @@ void xtensa_timer_irq(CPUXtensaState *env, uint32_t id, uint32_t active)
qemu_set_irq(env->irq_inputs[env->config->timerint[id]], active);
}

void xtensa_rearm_ccompare_timer(CPUXtensaState *env)
{
int i;
uint32_t wake_ccount = env->sregs[CCOUNT] - 1;

for (i = 0; i < env->config->nccompare; ++i) {
if (env->sregs[CCOMPARE + i] - env->sregs[CCOUNT] <
wake_ccount - env->sregs[CCOUNT]) {
wake_ccount = env->sregs[CCOMPARE + i];
}
}
env->wake_ccount = wake_ccount;
timer_mod(env->ccompare_timer, env->halt_clock +
(uint64_t)(wake_ccount - env->sregs[CCOUNT]) *
1000000 / env->config->clock_freq_khz);
}

static void xtensa_ccompare_cb(void *opaque)
{
XtensaCPU *cpu = opaque;
CPUXtensaState *env = &cpu->env;
CPUState *cs = CPU(cpu);
XtensaCcompareTimer *ccompare = opaque;
CPUXtensaState *env = ccompare->env;
unsigned i = ccompare - env->ccompare;

if (cs->halted) {
env->halt_clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
xtensa_advance_ccount(env, env->wake_ccount - env->sregs[CCOUNT]);
if (!cpu_has_work(cs)) {
env->sregs[CCOUNT] = env->wake_ccount + 1;
xtensa_rearm_ccompare_timer(env);
}
}
xtensa_timer_irq(env, i, 1);
}

void xtensa_irq_init(CPUXtensaState *env)
{
XtensaCPU *cpu = xtensa_env_get_cpu(env);

env->irq_inputs = (void **)qemu_allocate_irqs(
xtensa_set_irq, env, env->config->ninterrupt);
if (xtensa_option_enabled(env->config, XTENSA_OPTION_TIMER_INTERRUPT) &&
env->config->nccompare > 0) {
env->ccompare_timer =
timer_new_ns(QEMU_CLOCK_VIRTUAL, &xtensa_ccompare_cb, cpu);
if (xtensa_option_enabled(env->config, XTENSA_OPTION_TIMER_INTERRUPT)) {
unsigned i;

env->time_base = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
env->ccount_base = env->sregs[CCOUNT];
for (i = 0; i < env->config->nccompare; ++i) {
env->ccompare[i].env = env;
env->ccompare[i].timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
xtensa_ccompare_cb, env->ccompare + i);
}
}
}

Expand Down
16 changes: 11 additions & 5 deletions target/xtensa/cpu.h
Expand Up @@ -269,6 +269,8 @@ typedef enum {
INTTYPE_MAX
} interrupt_type;

struct CPUXtensaState;

typedef struct xtensa_tlb_entry {
uint32_t vaddr;
uint32_t paddr;
Expand Down Expand Up @@ -298,6 +300,11 @@ typedef struct XtensaGdbRegmap {
XtensaGdbReg reg[1 + 16 + 64 + 256 + 256];
} XtensaGdbRegmap;

typedef struct XtensaCcompareTimer {
struct CPUXtensaState *env;
QEMUTimer *timer;
} XtensaCcompareTimer;

struct XtensaConfig {
const char *name;
uint64_t options;
Expand Down Expand Up @@ -369,9 +376,10 @@ typedef struct CPUXtensaState {
bool runstall;
int pending_irq_level; /* level of last raised IRQ */
void **irq_inputs;
QEMUTimer *ccompare_timer;
uint32_t wake_ccount;
int64_t halt_clock;
XtensaCcompareTimer ccompare[MAX_NCCOMPARE];
uint64_t time_base;
uint64_t ccount_time;
uint32_t ccount_base;

int exception_taken;
unsigned static_vectors;
Expand Down Expand Up @@ -439,9 +447,7 @@ void xtensa_register_core(XtensaConfigList *node);
void check_interrupts(CPUXtensaState *s);
void xtensa_irq_init(CPUXtensaState *env);
void *xtensa_get_extint(CPUXtensaState *env, unsigned extint);
void xtensa_advance_ccount(CPUXtensaState *env, uint32_t d);
void xtensa_timer_irq(CPUXtensaState *env, uint32_t id, uint32_t active);
void xtensa_rearm_ccompare_timer(CPUXtensaState *env);
int cpu_xtensa_signal_handler(int host_signum, void *pinfo, void *puc);
void xtensa_cpu_list(FILE *f, fprintf_function cpu_fprintf);
void xtensa_sync_window_from_phys(CPUXtensaState *env);
Expand Down
5 changes: 3 additions & 2 deletions target/xtensa/helper.h
Expand Up @@ -18,8 +18,9 @@ DEF_HELPER_1(simcall, void, env)
DEF_HELPER_1(dump_state, void, env)

DEF_HELPER_3(waiti, void, env, i32, i32)
DEF_HELPER_3(timer_irq, void, env, i32, i32)
DEF_HELPER_2(advance_ccount, void, env, i32)
DEF_HELPER_1(update_ccount, void, env)
DEF_HELPER_2(wsr_ccount, void, env, i32)
DEF_HELPER_2(update_ccompare, void, env, i32)
DEF_HELPER_1(check_interrupts, void, env)
DEF_HELPER_3(check_atomctl, void, env, i32, i32)

Expand Down
33 changes: 25 additions & 8 deletions target/xtensa/op_helper.c
Expand Up @@ -398,22 +398,39 @@ void HELPER(waiti)(CPUXtensaState *env, uint32_t pc, uint32_t intlevel)
}

cpu = CPU(xtensa_env_get_cpu(env));
env->halt_clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
cpu->halted = 1;
if (xtensa_option_enabled(env->config, XTENSA_OPTION_TIMER_INTERRUPT)) {
xtensa_rearm_ccompare_timer(env);
}
HELPER(exception)(env, EXCP_HLT);
}

void HELPER(timer_irq)(CPUXtensaState *env, uint32_t id, uint32_t active)
void HELPER(update_ccount)(CPUXtensaState *env)
{
uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);

env->ccount_time = now;
env->sregs[CCOUNT] = env->ccount_base +
(uint32_t)((now - env->time_base) *
env->config->clock_freq_khz / 1000000);
}

void HELPER(wsr_ccount)(CPUXtensaState *env, uint32_t v)
{
xtensa_timer_irq(env, id, active);
int i;

HELPER(update_ccount)(env);
env->ccount_base += v - env->sregs[CCOUNT];
for (i = 0; i < env->config->nccompare; ++i) {
HELPER(update_ccompare)(env, i);
}
}

void HELPER(advance_ccount)(CPUXtensaState *env, uint32_t d)
void HELPER(update_ccompare)(CPUXtensaState *env, uint32_t i)
{
xtensa_advance_ccount(env, d);
uint64_t dcc;

HELPER(update_ccount)(env);
dcc = (uint64_t)(env->sregs[CCOMPARE + i] - env->sregs[CCOUNT] - 1) + 1;
timer_mod(env->ccompare[i].timer,
env->ccount_time + (dcc * 1000000) / env->config->clock_freq_khz);
}

void HELPER(check_interrupts)(CPUXtensaState *env)
Expand Down

0 comments on commit 59a71f7

Please sign in to comment.