/* * QEMU OpenRISC timer support * * Copyright (c) 2011-2012 Jia Liu * Zhizhou Zhang * * 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 . */ #include "qemu/osdep.h" #include "cpu.h" #include "migration/vmstate.h" #include "qemu/timer.h" #include "sysemu/reset.h" #define TIMER_PERIOD 50 /* 50 ns period for 20 MHz timer */ /* Tick Timer global state to allow all cores to be in sync */ typedef struct OR1KTimerState { uint32_t ttcr; uint32_t ttcr_offset; uint64_t clk_offset; } OR1KTimerState; static OR1KTimerState *or1k_timer; void cpu_openrisc_count_set(OpenRISCCPU *cpu, uint32_t val) { or1k_timer->ttcr = val; or1k_timer->ttcr_offset = val; or1k_timer->clk_offset = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); } uint32_t cpu_openrisc_count_get(OpenRISCCPU *cpu) { return or1k_timer->ttcr; } /* Add elapsed ticks to ttcr */ void cpu_openrisc_count_update(OpenRISCCPU *cpu) { uint64_t now; if (!cpu->env.is_counting) { return; } now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); or1k_timer->ttcr = or1k_timer->ttcr_offset + DIV_ROUND_UP(now - or1k_timer->clk_offset, TIMER_PERIOD); } /* Update the next timeout time as difference between ttmr and ttcr */ void cpu_openrisc_timer_update(OpenRISCCPU *cpu) { uint32_t wait; uint64_t now, next; if (!cpu->env.is_counting) { return; } cpu_openrisc_count_update(cpu); now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); if ((cpu->env.ttmr & TTMR_TP) <= (or1k_timer->ttcr & TTMR_TP)) { wait = TTMR_TP - (or1k_timer->ttcr & TTMR_TP) + 1; wait += cpu->env.ttmr & TTMR_TP; } else { wait = (cpu->env.ttmr & TTMR_TP) - (or1k_timer->ttcr & TTMR_TP); } next = now + (uint64_t)wait * TIMER_PERIOD; timer_mod(cpu->env.timer, next); } void cpu_openrisc_count_start(OpenRISCCPU *cpu) { cpu->env.is_counting = 1; cpu_openrisc_count_update(cpu); } void cpu_openrisc_count_stop(OpenRISCCPU *cpu) { timer_del(cpu->env.timer); cpu_openrisc_count_update(cpu); cpu->env.is_counting = 0; } static void openrisc_timer_cb(void *opaque) { OpenRISCCPU *cpu = opaque; if ((cpu->env.ttmr & TTMR_IE) && timer_expired(cpu->env.timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL))) { CPUState *cs = CPU(cpu); cpu->env.ttmr |= TTMR_IP; cs->interrupt_request |= CPU_INTERRUPT_TIMER; } switch (cpu->env.ttmr & TTMR_M) { case TIMER_NONE: break; case TIMER_INTR: /* Zero the count by applying a negative offset to the counter */ or1k_timer->ttcr_offset -= (cpu->env.ttmr & TTMR_TP); break; case TIMER_SHOT: cpu_openrisc_count_stop(cpu); break; case TIMER_CONT: break; } cpu_openrisc_timer_update(cpu); qemu_cpu_kick(CPU(cpu)); } /* Reset the per CPU counter state. */ static void openrisc_count_reset(void *opaque) { OpenRISCCPU *cpu = opaque; if (cpu->env.is_counting) { cpu_openrisc_count_stop(cpu); } cpu->env.ttmr = 0x00000000; } /* Reset the global timer state. */ static void openrisc_timer_reset(void *opaque) { OpenRISCCPU *cpu = opaque; cpu_openrisc_count_set(cpu, 0); } static const VMStateDescription vmstate_or1k_timer = { .name = "or1k_timer", .version_id = 2, .minimum_version_id = 2, .fields = (const VMStateField[]) { VMSTATE_UINT32(ttcr, OR1KTimerState), VMSTATE_UINT32(ttcr_offset, OR1KTimerState), VMSTATE_UINT64(clk_offset, OR1KTimerState), VMSTATE_END_OF_LIST() } }; void cpu_openrisc_clock_init(OpenRISCCPU *cpu) { cpu->env.timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &openrisc_timer_cb, cpu); qemu_register_reset(openrisc_count_reset, cpu); if (or1k_timer == NULL) { or1k_timer = g_new0(OR1KTimerState, 1); qemu_register_reset(openrisc_timer_reset, cpu); vmstate_register(NULL, 0, &vmstate_or1k_timer, or1k_timer); } }