On 07.09.2011, at 16:41, Fabien Chouteau wrote: > On 06/09/2011 21:33, Alexander Graf wrote: >> >> >> Am 01.09.2011 um 10:20 schrieb Fabien Chouteau <chout...@adacore.com>: >> >>> While working on the emulation of the freescale p2010 (e500v2) I realized >>> that >>> there's no implementation of booke's timers features. Currently mpc8544 uses >>> ppc_emb (ppc_emb_timers_init) which is close but not exactly like booke (for >>> example booke uses different SPR). >>> >>> Signed-off-by: Fabien Chouteau <chout...@adacore.com> >>> --- >>> Makefile.target | 2 +- >>> hw/ppc.c | 133 ++++++++-------------- >>> hw/ppc.h | 25 ++++- >>> hw/ppc4xx_devs.c | 2 +- >>> hw/ppc_booke.c | 263 >>> +++++++++++++++++++++++++++++++++++++++++++ >>> hw/ppce500_mpc8544ds.c | 4 +- >>> hw/virtex_ml507.c | 11 +-- >>> target-ppc/cpu.h | 29 +++++ >>> target-ppc/translate_init.c | 43 +++++++- >>> 9 files changed, 412 insertions(+), 100 deletions(-) >>> create mode 100644 hw/ppc_booke.c >>> >>> diff --git a/Makefile.target b/Makefile.target >>> index 07af4d4..c8704cd 100644 >>> --- a/Makefile.target >>> +++ b/Makefile.target >>> @@ -234,7 +234,7 @@ obj-i386-$(CONFIG_KVM) += kvmclock.o >>> obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o >>> >>> # shared objects >>> -obj-ppc-y = ppc.o >>> +obj-ppc-y = ppc.o ppc_booke.o >>> obj-ppc-y += vga.o >>> # PREP target >>> obj-ppc-y += i8259.o mc146818rtc.o >>> diff --git a/hw/ppc.c b/hw/ppc.c >>> index 8870748..bcb1e91 100644 >>> --- a/hw/ppc.c >>> +++ b/hw/ppc.c >>> @@ -50,7 +50,7 @@ >>> static void cpu_ppc_tb_stop (CPUState *env); >>> static void cpu_ppc_tb_start (CPUState *env); >>> >>> -static void ppc_set_irq (CPUState *env, int n_IRQ, int level) >>> +void ppc_set_irq(CPUState *env, int n_IRQ, int level) >>> { >>> unsigned int old_pending = env->pending_interrupts; >>> >>> @@ -423,25 +423,8 @@ void ppce500_irq_init (CPUState *env) >>> } >>> /*****************************************************************************/ >>> /* PowerPC time base and decrementer emulation */ >>> -struct ppc_tb_t { >>> - /* Time base management */ >>> - int64_t tb_offset; /* Compensation */ >>> - int64_t atb_offset; /* Compensation */ >>> - uint32_t tb_freq; /* TB frequency */ >>> - /* Decrementer management */ >>> - uint64_t decr_next; /* Tick for next decr interrupt */ >>> - uint32_t decr_freq; /* decrementer frequency */ >>> - struct QEMUTimer *decr_timer; >>> - /* Hypervisor decrementer management */ >>> - uint64_t hdecr_next; /* Tick for next hdecr interrupt */ >>> - struct QEMUTimer *hdecr_timer; >>> - uint64_t purr_load; >>> - uint64_t purr_start; >>> - void *opaque; >>> -}; >>> >>> -static inline uint64_t cpu_ppc_get_tb(ppc_tb_t *tb_env, uint64_t vmclk, >>> - int64_t tb_offset) >>> +uint64_t cpu_ppc_get_tb(ppc_tb_t *tb_env, uint64_t vmclk, int64_t >>> tb_offset) >>> { >>> /* TB time in tb periods */ >>> return muldiv64(vmclk, tb_env->tb_freq, get_ticks_per_sec()) + tb_offset; >>> @@ -611,10 +594,13 @@ static inline uint32_t _cpu_ppc_load_decr(CPUState >>> *env, uint64_t next) >>> int64_t diff; >>> >>> diff = next - qemu_get_clock_ns(vm_clock); >>> - if (diff >= 0) >>> + if (diff >= 0) { >>> decr = muldiv64(diff, tb_env->decr_freq, get_ticks_per_sec()); >>> - else >>> + } else if (env->insns_flags & PPC_BOOKE) { >>> + decr = 0; >> >> I don't think we should expose instruction interpreter details into the >> timing code. It'd be better to have a separate flag that gets set on the >> booke >> timer init function which would be stored in tb_env. Keeps things better >> separated :) > > Good idea, but how do we set the flags? ppc_booke_timers_init doesn't know > which processor is emulated.
We could have two different init functions. One for normal booke and one for e500. Or we pass in timer flags to the init function. > > >> >>> + } else { >>> decr = -muldiv64(-diff, tb_env->decr_freq, get_ticks_per_sec()); >>> + } >>> LOG_TB("%s: %08" PRIx32 "\n", __func__, decr); >>> >>> return decr; >>> @@ -678,18 +664,23 @@ static void __cpu_ppc_store_decr (CPUState *env, >>> uint64_t *nextp, >>> decr, value); >>> now = qemu_get_clock_ns(vm_clock); >>> next = now + muldiv64(value, get_ticks_per_sec(), tb_env->decr_freq); >>> - if (is_excp) >>> + if (is_excp) { >>> next += *nextp - now; >>> - if (next == now) >>> + } >>> + if (next == now) { >>> next++; >>> + } >>> *nextp = next; >>> /* Adjust timer */ >>> qemu_mod_timer(timer, next); >>> /* If we set a negative value and the decrementer was positive, >>> - * raise an exception. >>> + * raise an exception (not for booke). >>> */ >>> - if ((value & 0x80000000) && !(decr & 0x80000000)) >>> + if (!(env->insns_flags & PPC_BOOKE) >>> + && (value & 0x80000000) >>> + && !(decr & 0x80000000)) { >>> (*raise_excp)(env); >> >> Please make this a separate flag too. IIRC this is not unified behavior on >> all ppc CPUs, not even all server type ones. >> > > This come from a comment by Scott (CC'd), I don't know much about it. Can you > help me to find a good name for this feature? PPC_DECR_TRIGGER_ON_NEGATIVE? :) > > >>> + } >>> } >>> >>> static inline void _cpu_ppc_store_decr(CPUState *env, uint32_t decr, >>> @@ -806,11 +797,11 @@ uint32_t cpu_ppc601_load_rtcl (CPUState *env) >>> } >>> >>> /*****************************************************************************/ >>> -/* Embedded PowerPC timers */ >>> +/* PowerPC 40x timers */ >>> >>> /* PIT, FIT & WDT */ >>> -typedef struct ppcemb_timer_t ppcemb_timer_t; >>> -struct ppcemb_timer_t { >>> +typedef struct ppc40x_timer_t ppc40x_timer_t; >>> +struct ppc40x_timer_t { >>> uint64_t pit_reload; /* PIT auto-reload value */ >>> uint64_t fit_next; /* Tick for next FIT interrupt */ >>> struct QEMUTimer *fit_timer; >>> @@ -826,12 +817,12 @@ static void cpu_4xx_fit_cb (void *opaque) >>> { >>> CPUState *env; >>> ppc_tb_t *tb_env; >>> - ppcemb_timer_t *ppcemb_timer; >>> + ppc40x_timer_t *ppc40x_timer; >>> uint64_t now, next; >>> >>> env = opaque; >>> tb_env = env->tb_env; >>> - ppcemb_timer = tb_env->opaque; >>> + ppc40x_timer = tb_env->opaque; >>> now = qemu_get_clock_ns(vm_clock); >>> switch ((env->spr[SPR_40x_TCR] >> 24) & 0x3) { >>> case 0: >>> @@ -853,7 +844,7 @@ static void cpu_4xx_fit_cb (void *opaque) >>> next = now + muldiv64(next, get_ticks_per_sec(), tb_env->tb_freq); >>> if (next == now) >>> next++; >>> - qemu_mod_timer(ppcemb_timer->fit_timer, next); >>> + qemu_mod_timer(ppc40x_timer->fit_timer, next); >>> env->spr[SPR_40x_TSR] |= 1 << 26; >>> if ((env->spr[SPR_40x_TCR] >> 23) & 0x1) >>> ppc_set_irq(env, PPC_INTERRUPT_FIT, 1); >>> @@ -865,11 +856,11 @@ static void cpu_4xx_fit_cb (void *opaque) >>> /* Programmable interval timer */ >>> static void start_stop_pit (CPUState *env, ppc_tb_t *tb_env, int is_excp) >>> { >>> - ppcemb_timer_t *ppcemb_timer; >>> + ppc40x_timer_t *ppc40x_timer; >>> uint64_t now, next; >>> >>> - ppcemb_timer = tb_env->opaque; >>> - if (ppcemb_timer->pit_reload <= 1 || >>> + ppc40x_timer = tb_env->opaque; >>> + if (ppc40x_timer->pit_reload <= 1 || >>> !((env->spr[SPR_40x_TCR] >> 26) & 0x1) || >>> (is_excp && !((env->spr[SPR_40x_TCR] >> 22) & 0x1))) { >>> /* Stop PIT */ >>> @@ -877,9 +868,9 @@ static void start_stop_pit (CPUState *env, ppc_tb_t >>> *tb_env, int is_excp) >>> qemu_del_timer(tb_env->decr_timer); >>> } else { >>> LOG_TB("%s: start PIT %016" PRIx64 "\n", >>> - __func__, ppcemb_timer->pit_reload); >>> + __func__, ppc40x_timer->pit_reload); >>> now = qemu_get_clock_ns(vm_clock); >>> - next = now + muldiv64(ppcemb_timer->pit_reload, >>> + next = now + muldiv64(ppc40x_timer->pit_reload, >>> get_ticks_per_sec(), tb_env->decr_freq); >>> if (is_excp) >>> next += tb_env->decr_next - now; >>> @@ -894,21 +885,21 @@ static void cpu_4xx_pit_cb (void *opaque) >>> { >>> CPUState *env; >>> ppc_tb_t *tb_env; >>> - ppcemb_timer_t *ppcemb_timer; >>> + ppc40x_timer_t *ppc40x_timer; >>> >>> env = opaque; >>> tb_env = env->tb_env; >>> - ppcemb_timer = tb_env->opaque; >>> + ppc40x_timer = tb_env->opaque; >>> env->spr[SPR_40x_TSR] |= 1 << 27; >>> if ((env->spr[SPR_40x_TCR] >> 26) & 0x1) >>> - ppc_set_irq(env, ppcemb_timer->decr_excp, 1); >>> + ppc_set_irq(env, ppc40x_timer->decr_excp, 1); >>> start_stop_pit(env, tb_env, 1); >>> LOG_TB("%s: ar %d ir %d TCR " TARGET_FMT_lx " TSR " TARGET_FMT_lx " " >>> "%016" PRIx64 "\n", __func__, >>> (int)((env->spr[SPR_40x_TCR] >> 22) & 0x1), >>> (int)((env->spr[SPR_40x_TCR] >> 26) & 0x1), >>> env->spr[SPR_40x_TCR], env->spr[SPR_40x_TSR], >>> - ppcemb_timer->pit_reload); >>> + ppc40x_timer->pit_reload); >>> } >>> >>> /* Watchdog timer */ >>> @@ -916,12 +907,12 @@ static void cpu_4xx_wdt_cb (void *opaque) >>> { >>> CPUState *env; >>> ppc_tb_t *tb_env; >>> - ppcemb_timer_t *ppcemb_timer; >>> + ppc40x_timer_t *ppc40x_timer; >>> uint64_t now, next; >>> >>> env = opaque; >>> tb_env = env->tb_env; >>> - ppcemb_timer = tb_env->opaque; >>> + ppc40x_timer = tb_env->opaque; >>> now = qemu_get_clock_ns(vm_clock); >>> switch ((env->spr[SPR_40x_TCR] >> 30) & 0x3) { >>> case 0: >>> @@ -948,13 +939,13 @@ static void cpu_4xx_wdt_cb (void *opaque) >>> switch ((env->spr[SPR_40x_TSR] >> 30) & 0x3) { >>> case 0x0: >>> case 0x1: >>> - qemu_mod_timer(ppcemb_timer->wdt_timer, next); >>> - ppcemb_timer->wdt_next = next; >>> + qemu_mod_timer(ppc40x_timer->wdt_timer, next); >>> + ppc40x_timer->wdt_next = next; >>> env->spr[SPR_40x_TSR] |= 1 << 31; >>> break; >>> case 0x2: >>> - qemu_mod_timer(ppcemb_timer->wdt_timer, next); >>> - ppcemb_timer->wdt_next = next; >>> + qemu_mod_timer(ppc40x_timer->wdt_timer, next); >>> + ppc40x_timer->wdt_next = next; >>> env->spr[SPR_40x_TSR] |= 1 << 30; >>> if ((env->spr[SPR_40x_TCR] >> 27) & 0x1) >>> ppc_set_irq(env, PPC_INTERRUPT_WDT, 1); >>> @@ -982,12 +973,12 @@ static void cpu_4xx_wdt_cb (void *opaque) >>> void store_40x_pit (CPUState *env, target_ulong val) >>> { >>> ppc_tb_t *tb_env; >>> - ppcemb_timer_t *ppcemb_timer; >>> + ppc40x_timer_t *ppc40x_timer; >>> >>> tb_env = env->tb_env; >>> - ppcemb_timer = tb_env->opaque; >>> + ppc40x_timer = tb_env->opaque; >>> LOG_TB("%s val" TARGET_FMT_lx "\n", __func__, val); >>> - ppcemb_timer->pit_reload = val; >>> + ppc40x_timer->pit_reload = val; >>> start_stop_pit(env, tb_env, 0); >>> } >>> >>> @@ -996,31 +987,7 @@ target_ulong load_40x_pit (CPUState *env) >>> return cpu_ppc_load_decr(env); >>> } >>> >>> -void store_booke_tsr (CPUState *env, target_ulong val) >>> -{ >>> - ppc_tb_t *tb_env = env->tb_env; >>> - ppcemb_timer_t *ppcemb_timer; >>> - >>> - ppcemb_timer = tb_env->opaque; >>> - >>> - LOG_TB("%s: val " TARGET_FMT_lx "\n", __func__, val); >>> - env->spr[SPR_40x_TSR] &= ~(val & 0xFC000000); >>> - if (val & 0x80000000) >>> - ppc_set_irq(env, ppcemb_timer->decr_excp, 0); >>> -} >>> - >>> -void store_booke_tcr (CPUState *env, target_ulong val) >>> -{ >>> - ppc_tb_t *tb_env; >>> - >>> - tb_env = env->tb_env; >>> - LOG_TB("%s: val " TARGET_FMT_lx "\n", __func__, val); >>> - env->spr[SPR_40x_TCR] = val & 0xFFC00000; >>> - start_stop_pit(env, tb_env, 1); >>> - cpu_4xx_wdt_cb(env); >>> -} >>> - >>> -static void ppc_emb_set_tb_clk (void *opaque, uint32_t freq) >>> +static void ppc_40x_set_tb_clk (void *opaque, uint32_t freq) >>> { >>> CPUState *env = opaque; >>> ppc_tb_t *tb_env = env->tb_env; >>> @@ -1032,30 +999,30 @@ static void ppc_emb_set_tb_clk (void *opaque, >>> uint32_t freq) >>> /* XXX: we should also update all timers */ >>> } >>> >>> -clk_setup_cb ppc_emb_timers_init (CPUState *env, uint32_t freq, >>> +clk_setup_cb ppc_40x_timers_init (CPUState *env, uint32_t freq, >>> unsigned int decr_excp) >>> { >>> ppc_tb_t *tb_env; >>> - ppcemb_timer_t *ppcemb_timer; >>> + ppc40x_timer_t *ppc40x_timer; >>> >>> tb_env = g_malloc0(sizeof(ppc_tb_t)); >>> env->tb_env = tb_env; >>> - ppcemb_timer = g_malloc0(sizeof(ppcemb_timer_t)); >>> + ppc40x_timer = g_malloc0(sizeof(ppc40x_timer_t)); >>> tb_env->tb_freq = freq; >>> tb_env->decr_freq = freq; >>> - tb_env->opaque = ppcemb_timer; >>> + tb_env->opaque = ppc40x_timer; >>> LOG_TB("%s freq %" PRIu32 "\n", __func__, freq); >>> - if (ppcemb_timer != NULL) { >>> + if (ppc40x_timer != NULL) { >>> /* We use decr timer for PIT */ >>> tb_env->decr_timer = qemu_new_timer_ns(vm_clock, &cpu_4xx_pit_cb, >>> env); >>> - ppcemb_timer->fit_timer = >>> + ppc40x_timer->fit_timer = >>> qemu_new_timer_ns(vm_clock, &cpu_4xx_fit_cb, env); >>> - ppcemb_timer->wdt_timer = >>> + ppc40x_timer->wdt_timer = >>> qemu_new_timer_ns(vm_clock, &cpu_4xx_wdt_cb, env); >>> - ppcemb_timer->decr_excp = decr_excp; >>> + ppc40x_timer->decr_excp = decr_excp; >>> } >>> >>> - return &ppc_emb_set_tb_clk; >>> + return &ppc_40x_set_tb_clk; >>> } >>> >>> /*****************************************************************************/ >>> diff --git a/hw/ppc.h b/hw/ppc.h >>> index 3ccf134..16df16a 100644 >>> --- a/hw/ppc.h >>> +++ b/hw/ppc.h >>> @@ -1,3 +1,5 @@ >>> +void ppc_set_irq (CPUState *env, int n_IRQ, int level); >>> + >>> /* PowerPC hardware exceptions management helpers */ >>> typedef void (*clk_setup_cb)(void *opaque, uint32_t freq); >>> typedef struct clk_setup_t clk_setup_t; >>> @@ -11,6 +13,24 @@ static inline void clk_setup (clk_setup_t *clk, uint32_t >>> freq) >>> (*clk->cb)(clk->opaque, freq); >>> } >>> >>> +struct ppc_tb_t { >>> + /* Time base management */ >>> + int64_t tb_offset; /* Compensation */ >>> + int64_t atb_offset; /* Compensation */ >>> + uint32_t tb_freq; /* TB frequency */ >>> + /* Decrementer management */ >>> + uint64_t decr_next; /* Tick for next decr interrupt */ >>> + uint32_t decr_freq; /* decrementer frequency */ >>> + struct QEMUTimer *decr_timer; >>> + /* Hypervisor decrementer management */ >>> + uint64_t hdecr_next; /* Tick for next hdecr interrupt */ >>> + struct QEMUTimer *hdecr_timer; >>> + uint64_t purr_load; >>> + uint64_t purr_start; >>> + void *opaque; >>> +}; >>> + >>> +uint64_t cpu_ppc_get_tb(ppc_tb_t *tb_env, uint64_t vmclk, int64_t >>> tb_offset); >>> clk_setup_cb cpu_ppc_tb_init (CPUState *env, uint32_t freq); >>> /* Embedded PowerPC DCR management */ >>> typedef uint32_t (*dcr_read_cb)(void *opaque, int dcrn); >>> @@ -19,7 +39,7 @@ int ppc_dcr_init (CPUState *env, int >>> (*dcr_read_error)(int dcrn), >>> int (*dcr_write_error)(int dcrn)); >>> int ppc_dcr_register (CPUState *env, int dcrn, void *opaque, >>> dcr_read_cb drc_read, dcr_write_cb dcr_write); >>> -clk_setup_cb ppc_emb_timers_init (CPUState *env, uint32_t freq, >>> +clk_setup_cb ppc_40x_timers_init (CPUState *env, uint32_t freq, >>> unsigned int decr_excp); >>> >>> /* Embedded PowerPC reset */ >>> @@ -55,3 +75,6 @@ enum { >>> #define FW_CFG_PPC_KVM_PID (FW_CFG_ARCH_LOCAL + 0x07) >>> >>> #define PPC_SERIAL_MM_BAUDBASE 399193 >>> + >>> +/* ppc_booke.c */ >>> +void ppc_booke_timers_init (CPUState *env, uint32_t freq); >>> diff --git a/hw/ppc4xx_devs.c b/hw/ppc4xx_devs.c >>> index 349f046..d18caa4 100644 >>> --- a/hw/ppc4xx_devs.c >>> +++ b/hw/ppc4xx_devs.c >>> @@ -56,7 +56,7 @@ CPUState *ppc4xx_init (const char *cpu_model, >>> cpu_clk->cb = NULL; /* We don't care about CPU clock frequency changes */ >>> cpu_clk->opaque = env; >>> /* Set time-base frequency to sysclk */ >>> - tb_clk->cb = ppc_emb_timers_init(env, sysclk, PPC_INTERRUPT_PIT); >>> + tb_clk->cb = ppc_40x_timers_init(env, sysclk, PPC_INTERRUPT_PIT); >>> tb_clk->opaque = env; >>> ppc_dcr_init(env, NULL, NULL); >>> /* Register qemu callbacks */ >>> diff --git a/hw/ppc_booke.c b/hw/ppc_booke.c >>> new file mode 100644 >>> index 0000000..35f11ca >>> --- /dev/null >>> +++ b/hw/ppc_booke.c >>> @@ -0,0 +1,263 @@ >>> +/* >>> + * QEMU PowerPC Booke hardware System Emulator >>> + * >>> + * Copyright (c) 2011 AdaCore >>> + * >>> + * Permission is hereby granted, free of charge, to any person obtaining a >>> copy >>> + * of this software and associated documentation files (the "Software"), >>> to deal >>> + * in the Software without restriction, including without limitation the >>> rights >>> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or >>> sell >>> + * copies of the Software, and to permit persons to whom the Software is >>> + * furnished to do so, subject to the following conditions: >>> + * >>> + * The above copyright notice and this permission notice shall be included >>> in >>> + * all copies or substantial portions of the Software. >>> + * >>> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS >>> OR >>> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, >>> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL >>> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR >>> OTHER >>> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING >>> FROM, >>> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS >>> IN >>> + * THE SOFTWARE. >>> + */ >>> +#include "hw.h" >>> +#include "ppc.h" >>> +#include "qemu-timer.h" >>> +#include "sysemu.h" >>> +#include "nvram.h" >>> +#include "qemu-log.h" >>> +#include "loader.h" >>> + >>> + >>> +/* Timer Control Register */ >>> + >>> +#define TCR_WP_SHIFT 30 /* Watchdog Timer Period */ >>> +#define TCR_WP_MASK (0x3 << TCR_WP_SHIFT) >>> +#define TCR_WRC_SHIFT 28 /* Watchdog Timer Reset Control */ >>> +#define TCR_WRC_MASK (0x3 << TCR_WRC_SHIFT) >>> +#define TCR_WIE (1 << 27) /* Watchdog Timer Interrupt Enable */ >>> +#define TCR_DIE (1 << 26) /* Decrementer Interrupt Enable */ >>> +#define TCR_FP_SHIFT 24 /* Fixed-Interval Timer Period */ >>> +#define TCR_FP_MASK (0x3 << TCR_FP_SHIFT) >>> +#define TCR_FIE (1 << 23) /* Fixed-Interval Timer Interrupt Enable */ >>> +#define TCR_ARE (1 << 22) /* Auto-Reload Enable */ >>> + >>> +/* Timer Control Register (e500 specific fields) */ >>> + >>> +#define TCR_E500_FPEXT_SHIFT 13 /* Fixed-Interval Timer Period Extension */ >>> +#define TCR_E500_FPEXT_MASK (0xf << TCR_E500_FPEXT_SHIFT) >>> +#define TCR_E500_WPEXT_SHIFT 17 /* Watchdog Timer Period Extension */ >>> +#define TCR_E500_WPEXT_MASK (0xf << TCR_E500_WPEXT_SHIFT) >>> + >>> +/* Timer Status Register */ >>> + >>> +#define TSR_FIS (1 << 26) /* Fixed-Interval Timer Interrupt Status */ >>> +#define TSR_DIS (1 << 27) /* Decrementer Interrupt Status */ >>> +#define TSR_WRS_SHIFT 28 /* Watchdog Timer Reset Status */ >>> +#define TSR_WRS_MASK (0x3 << TSR_WRS_SHIFT) >>> +#define TSR_WIS (1 << 30) /* Watchdog Timer Interrupt Status */ >>> +#define TSR_ENW (1 << 31) /* Enable Next Watchdog Timer */ >>> + >>> +typedef struct booke_timer_t booke_timer_t; >>> +struct booke_timer_t { >>> + >>> + uint64_t fit_next; >>> + struct QEMUTimer *fit_timer; >>> + >>> + uint64_t wdt_next; >>> + struct QEMUTimer *wdt_timer; >>> +}; >>> + >>> +/* Return the location of the bit of time base at which the FIT will raise >>> an >>> + interrupt */ >>> +static uint8_t booke_get_fit_target(CPUState *env) >>> +{ >>> + uint8_t fp = (env->spr[SPR_BOOKE_TCR] & TCR_FP_MASK) >> TCR_FP_SHIFT; >>> + >>> + /* Only for e500 */ >>> + if (env->insns_flags2 & PPC2_E500) { >> >> Same here again :). Do you have test cases for fit? IIRC Linux doesn't use >> it. > > VxWorks uses it. If even remotely possible, I'd really like to have something in my portfolio of guest code to test this case - especially given that KVM implements its own timers. I assume your binary is non-redistributable? > >> >>> + uint32_t fpext = (env->spr[SPR_BOOKE_TCR] & TCR_E500_FPEXT_MASK) >>> + >> TCR_E500_FPEXT_SHIFT; >>> + fp = 63 - (fp | fpext << 2); >>> + } else { >>> + fp = env->fit_period[fp]; >>> + } >>> + >>> + return fp; >>> +} >>> + >>> +/* Return the location of the bit of time base at which the WDT will raise >>> an >>> + interrupt */ >>> +static uint8_t booke_get_wdt_target(CPUState *env) >>> +{ >>> + uint8_t wp = (env->spr[SPR_BOOKE_TCR] & TCR_WP_MASK) >> TCR_WP_SHIFT; >>> + >>> + /* Only for e500 */ >>> + if (env->insns_flags2 & PPC2_E500) { >>> + uint32_t wpext = (env->spr[SPR_BOOKE_TCR] & TCR_E500_WPEXT_MASK) >>> + >> TCR_E500_WPEXT_SHIFT; >>> + wp = 63 - (wp | wpext << 2); >>> + } else { >>> + wp = env->wdt_period[wp]; >>> + } >>> + >>> + return wp; >>> +} >>> + >>> +static void booke_update_fixed_timer(CPUState *env, >>> + uint8_t target_bit, >>> + uint64_t *next, >>> + struct QEMUTimer *timer) >>> +{ >>> + ppc_tb_t *tb_env = env->tb_env; >>> + uint64_t lapse; >>> + uint64_t tb; >>> + uint64_t period = 1 << (target_bit + 1); >>> + uint64_t now; >>> + >>> + now = qemu_get_clock_ns(vm_clock); >>> + tb = cpu_ppc_get_tb(tb_env, now, tb_env->tb_offset); >>> + >>> + lapse = period - ((tb - (1 << target_bit)) & (period - 1)); >>> + >>> + *next = now + muldiv64(lapse, get_ticks_per_sec(), tb_env->tb_freq); >>> + >>> + if (*next == now) { >>> + (*next)++; >> >> Huh? If we hit the timer we don't fire it? > > Yes we do, but one nanosecond later :) Well, why trigger a timer when we can just as well call the callback immediately? :) > >> >>> + } >>> + >>> + qemu_mod_timer(timer, *next); >>> +} >>> + >>> +static void booke_decr_cb(void *opaque) >>> +{ >>> + CPUState *env; >>> + ppc_tb_t *tb_env; >>> + booke_timer_t *booke_timer; >>> + >>> + env = opaque; >>> + tb_env = env->tb_env; >>> + booke_timer = tb_env->opaque; >>> + env->spr[SPR_BOOKE_TSR] |= TSR_DIS; >>> + if (env->spr[SPR_BOOKE_TCR] & TCR_DIE) { >>> + ppc_set_irq(env, PPC_INTERRUPT_DECR, 1); >>> + } >> >> You will need this more often - also for the TCR write case - so please put >> the 3 lines above into a separate function and just call that here :) > > Actually the checks are never exactly the same, here we test DIE in TCR... Sure, we have 3 different tests for the 3 different bits we can potentially set in TCR. The check always ends up being the same though: if (TSR & bit && TCR & bit) set_irq(irq_for_bit); Most device emulators have a simple function for this called "update_irq" that checks for all the bits and sets the respective irq lines. > > >>> + >>> + if (env->spr[SPR_BOOKE_TCR] & TCR_ARE) { >>> + /* Auto Reload */ >>> + cpu_ppc_store_decr(env, env->spr[SPR_BOOKE_DECAR]); >>> + } >> >> Ah nice - never seen this one used. Do you have a test case? >> > > VxWorks :) > > >>> +} >>> + >>> +static void booke_fit_cb(void *opaque) >>> +{ >>> + CPUState *env; >>> + ppc_tb_t *tb_env; >>> + booke_timer_t *booke_timer; >>> + >>> + env = opaque; >>> + tb_env = env->tb_env; >>> + booke_timer = tb_env->opaque; >>> + env->spr[SPR_BOOKE_TSR] |= TSR_FIS; >>> + if (env->spr[SPR_BOOKE_TCR] & TCR_FIE) { >>> + ppc_set_irq(env, PPC_INTERRUPT_FIT, 1); >>> + } >> >> Same as above :) >> >>> + >>> + booke_update_fixed_timer(env, >>> + booke_get_fit_target(env), >>> + &booke_timer->fit_next, >>> + booke_timer->fit_timer); >>> +} >>> + >>> +static void booke_wdt_cb(void *opaque) >>> +{ >>> + CPUState *env; >>> + ppc_tb_t *tb_env; >>> + booke_timer_t *booke_timer; >>> + >>> + env = opaque; >>> + tb_env = env->tb_env; >>> + booke_timer = tb_env->opaque; >>> + >>> + /* TODO: There's lots of complicated stuff to do here */ >>> + >>> + booke_update_fixed_timer(env, >>> + booke_get_wdt_target(env), >>> + &booke_timer->wdt_next, >>> + booke_timer->wdt_timer); >>> +} >>> + >>> +void store_booke_tsr(CPUState *env, target_ulong val) >>> +{ >>> + ppc_tb_t *tb_env = env->tb_env; >>> + booke_timer_t *booke_timer; >>> + >>> + booke_timer = tb_env->opaque; >>> + >>> + env->spr[SPR_BOOKE_TSR] &= ~val; >>> + >>> + if (val & TSR_DIS) { >>> + ppc_set_irq(env, PPC_INTERRUPT_DECR, 0); >>> + } >>> + >>> + if (val & TSR_FIS) { >>> + ppc_set_irq(env, PPC_INTERRUPT_FIT, 0); >>> + } >>> + >>> + if (val & TSR_WIS) { >>> + ppc_set_irq(env, PPC_INTERRUPT_WDT, 0); >>> + } >> >> Why do you need the above? Shouldn't they still be active if the guest didn't >> reset the bit? This is probably hiding a bug in the interrupt delivery >> mechanism automatically unmasking an irq bit when it's delivered, right? > > They are active until the bit is cleared by user, and TSR is a > write-1-to-clear > register. Yes, that's what I mean. There shouldn't be a need to set the irq again because it should still be active. These interrupts are level based. > >>> +} >>> + >>> +void store_booke_tcr(CPUState *env, target_ulong val) >>> +{ >>> + ppc_tb_t *tb_env = env->tb_env; >>> + booke_timer_t *booke_timer = tb_env->opaque; >>> + >>> + tb_env = env->tb_env; >>> + env->spr[SPR_BOOKE_TCR] = val; >>> + >>> + booke_update_fixed_timer(env, >>> + booke_get_fit_target(env), >>> + &booke_timer->fit_next, >>> + booke_timer->fit_timer); >>> + >>> + booke_update_fixed_timer(env, >>> + booke_get_wdt_target(env), >>> + &booke_timer->wdt_next, >>> + booke_timer->wdt_timer); >>> + >>> + if (val & TCR_DIE && env->spr[SPR_BOOKE_TSR] & TSR_DIS) { >>> + ppc_set_irq(env, PPC_INTERRUPT_DECR, 1); >>> + } >>> + >>> + if (val & TCR_FIE && env->spr[SPR_BOOKE_TSR] & TSR_FIS) { >>> + ppc_set_irq(env, PPC_INTERRUPT_FIT, 1); >>> + } >>> + >>> + if (val & TCR_WIE && env->spr[SPR_BOOKE_TSR] & TSR_WIS) { >>> + ppc_set_irq(env, PPC_INTERRUPT_WDT, 1); >>> + } >> >> Here is the second user of the checks. It really does make sense to only have >> them in a single spot, so that ever ppc_set_irq(DECR) always goes through the >> TSR and TCR checks for example :). > > ...here we test DIE in TCR and DIS in TSR. Yes, both of which are basically the prerequisites for actually triggering the internal DECR interrupt line. > >>> +} >>> + >>> +void ppc_booke_timers_init(CPUState *env, uint32_t freq) >>> +{ >>> + ppc_tb_t *tb_env; >>> + booke_timer_t *booke_timer; >>> + >>> + tb_env = g_malloc0(sizeof(ppc_tb_t)); >>> + booke_timer = g_malloc0(sizeof(booke_timer_t)); >>> + >>> + env->tb_env = tb_env; >>> + >>> + tb_env->tb_freq = freq; >>> + tb_env->decr_freq = freq; >>> + tb_env->opaque = booke_timer; >>> + tb_env->decr_timer = qemu_new_timer_ns(vm_clock, &booke_decr_cb, env); >>> + >>> + booke_timer->fit_timer = >>> + qemu_new_timer_ns(vm_clock, &booke_fit_cb, env); >>> + booke_timer->wdt_timer = >>> + qemu_new_timer_ns(vm_clock, &booke_wdt_cb, env); >>> +} >>> diff --git a/hw/ppce500_mpc8544ds.c b/hw/ppce500_mpc8544ds.c >>> index 1274a3e..b8aa0d0 100644 >>> --- a/hw/ppce500_mpc8544ds.c >>> +++ b/hw/ppce500_mpc8544ds.c >>> @@ -252,9 +252,7 @@ static void mpc8544ds_init(ram_addr_t ram_size, >>> exit(1); >>> } >>> >>> - /* XXX register timer? */ >>> - ppc_emb_timers_init(env, 400000000, PPC_INTERRUPT_DECR); >>> - ppc_dcr_init(env, NULL, NULL); >>> + ppc_booke_timers_init(env, 400000000); >>> >>> /* Register reset handler */ >>> qemu_register_reset(mpc8544ds_cpu_reset, env); >>> diff --git a/hw/virtex_ml507.c b/hw/virtex_ml507.c >>> index 333050c..dccaea3 100644 >>> --- a/hw/virtex_ml507.c >>> +++ b/hw/virtex_ml507.c >>> @@ -81,7 +81,6 @@ static void mmubooke_create_initial_mapping(CPUState *env, >>> static CPUState *ppc440_init_xilinx(ram_addr_t *ram_size, >>> int do_init, >>> const char *cpu_model, >>> - clk_setup_t *cpu_clk, clk_setup_t >>> *tb_clk, >>> uint32_t sysclk) >>> { >>> CPUState *env; >>> @@ -93,11 +92,7 @@ static CPUState *ppc440_init_xilinx(ram_addr_t *ram_size, >>> exit(1); >>> } >>> >>> - cpu_clk->cb = NULL; /* We don't care about CPU clock frequency changes >>> */ >>> - cpu_clk->opaque = env; >>> - /* Set time-base frequency to sysclk */ >>> - tb_clk->cb = ppc_emb_timers_init(env, sysclk, PPC_INTERRUPT_DECR); >>> - tb_clk->opaque = env; >>> + ppc_booke_timers_init(env, sysclk); >>> >>> ppc_dcr_init(env, NULL, NULL); >>> >>> @@ -198,7 +193,6 @@ static void virtex_init(ram_addr_t ram_size, >>> ram_addr_t phys_ram; >>> ram_addr_t phys_flash; >>> qemu_irq irq[32], *cpu_irq; >>> - clk_setup_t clk_setup[7]; >>> int kernel_size; >>> int i; >>> >>> @@ -208,8 +202,7 @@ static void virtex_init(ram_addr_t ram_size, >>> } >>> >>> memset(clk_setup, 0, sizeof(clk_setup)); >>> - env = ppc440_init_xilinx(&ram_size, 1, cpu_model, &clk_setup[0], >>> - &clk_setup[1], 400000000); >>> + env = ppc440_init_xilinx(&ram_size, 1, cpu_model, 400000000); >>> qemu_register_reset(main_cpu_reset, env); >>> >>> phys_ram = qemu_ram_alloc(NULL, "ram", ram_size); >>> diff --git a/target-ppc/cpu.h b/target-ppc/cpu.h >>> index b8d42e0..be0d79c 100644 >>> --- a/target-ppc/cpu.h >>> +++ b/target-ppc/cpu.h >>> @@ -1010,8 +1010,35 @@ struct CPUPPCState { >>> #if !defined(CONFIG_USER_ONLY) >>> void *load_info; /* Holds boot loading state. */ >>> #endif >>> + >>> + /* booke timers */ >>> + >>> + /* Specifies bit locations of the Time Base used to signal a fixed >>> timer >>> + * exception on a transition from 0 to 1. (watchdog or fixed-interval >>> timer) >>> + * >>> + * 0 selects the least significant bit. >>> + * 63 selects the most significant bit. >>> + */ >>> + uint8_t fit_period[4]; >>> + uint8_t wdt_period[4]; >>> }; >>> >>> +#define SET_FIT_PERIOD(a_, b_, c_, d_) \ >>> +do { \ >>> + env->fit_period[0] = (a_); \ >>> + env->fit_period[1] = (b_); \ >>> + env->fit_period[2] = (c_); \ >>> + env->fit_period[3] = (d_); \ >>> + } while (0) >>> + >>> +#define SET_WDT_PERIOD(a_, b_, c_, d_) \ >>> +do { \ >>> + env->wdt_period[0] = (a_); \ >>> + env->wdt_period[1] = (b_); \ >>> + env->wdt_period[2] = (c_); \ >>> + env->wdt_period[3] = (d_); \ >>> + } while (0) >>> + >>> #if !defined(CONFIG_USER_ONLY) >>> /* Context used internally during MMU translations */ >>> typedef struct mmu_ctx_t mmu_ctx_t; >>> @@ -1806,6 +1833,8 @@ enum { >>> >>> /* BookE 2.06 PowerPC specification >>> */ >>> PPC2_BOOKE206 = 0x0000000000000001ULL, >>> + /* e500 support >>> */ >>> + PPC2_E500 = 0x0000000000000002ULL, >> >> I don't think we should have an e500 inst target. It should be enough to have >> an SPE inst target and specific SPR init functions for e500, no? Keep in mind >> that these flags are only meant to be used by the instruction interpreter. > > Yes sure, it was the easy way to implement e500 features in the timers, but > now > I will remove it. > >> Very nice patch however! Thanks a lot for sitting down and fixing the timer >> mess on booke that we currently have. > > You are welcome, It's my thank you gift for the MMU ;) Haha thanks :). So you're going to fix the MPIC mess for me implementing SMP support? :D > >> Since you definitely know your way around booke timekeeping code, could you >> please also take a look at the decr patches on kvm-ppc@vger that Liu posted >> recently? > > I don't know anything about kvm but I can take a look. Do you have the name of > this patch? Sure, it's "KVM: PPC: booke: Improve timer register emulation". Thanks a lot for looking at it! Alex