The Bochs internal timers are required to provide timer features in the device emulation and for the interaction between simulator and gui. They are implemented in the bx_pc_system_c class and driven by the cpu. When programming a timer the interval is specified in useconds and the timer code translates the value to cpu ticks using the IPS value. In the original implementation the cpu object calls a timer method to increment the system time by one tick after completing one instruction. If a timer has expired, the related timer handler function is called. Now it is also possible to execute a number of cpu instructions, finally update the timer subsystem with this number and possibly call several timer handlers. Here are some examples for timers in the devices and gui code:
the PIT (i82C54) system timer at 18.2 Hz
the CMOS RTC one-second-timer
the display update timer (set up with "vga: update_freq=X")
the devices timer (polls keyboard/mouse events from the gui every 1 emulated msecond)
the LED auto-off timer (indicating data transfer for min 0.5 seconds)
the synchronization timers (realtime/slowdown) are also based on the standard timers
These are the capabilities of the Bochs internal timers:
register / unregister at runtime
activate / deactivate at runtime
timer period changeable
one-shot or continuous mode
Here are the timer-related definitions and members in pc_system.h:
#define BX_MAX_TIMERS 64 #define BX_NULL_TIMER_HANDLE 10000 typedef void (*bx_timer_handler_t)(void *); struct { bool inUse; // Timer slot is in-use (currently registered). Bit64u period; // Timer periodocity in cpu ticks. Bit64u timeToFire; // Time to fire next (in absolute ticks). bool active; // 0=inactive, 1=active. bool continuous; // 0=one-shot timer, 1=continuous periodicity. bx_timer_handler_t funct; // A callback function for when the // timer fires. void *this_ptr; // The this-> pointer for C++ callbacks // has to be stored as well. #define BxMaxTimerIDLen 32 char id[BxMaxTimerIDLen]; // String ID of timer. Bit32u param; // Device-specific value assigned to timer (optional) } timer[BX_MAX_TIMERS]; unsigned numTimers; // Number of currently allocated timers. unsigned triggeredTimer; // ID of the actually triggered timer. Bit32u currCountdown; // Current countdown ticks value (decrements to 0). Bit32u currCountdownPeriod; // Length of current countdown period. Bit64u ticksTotal; // Num ticks total since start of emulator execution. Bit64u lastTimeUsec; // Last sequentially read time in usec. Bit64u usecSinceLast; // Number of useconds claimed since then. // A special null timer is always inserted in the timer[0] slot. This // make sure that at least one timer is always active, and that the // duration is always less than a maximum 32-bit integer, so a 32-bit // counter can be used for the current countdown. static const Bit64u NullTimerInterval; static void nullTimer(void* this_ptr);
These are the public timer-related methods for timer control, driving the timers with the cpu and retrieving the internal time implemented in the bx_pc_system_c class:
void initialize(Bit32u ips); int register_timer(void *this_ptr, bx_timer_handler_t, Bit32u useconds, bool continuous, bool active, const char *id); bool unregisterTimer(unsigned timerID); void setTimerParam(unsigned timerID, Bit32u param); void start_timers(void); void activate_timer(unsigned timer_index, Bit32u useconds, bool continuous); void deactivate_timer(unsigned timer_index); unsigned triggeredTimerID(void) { return triggeredTimer; } Bit32u triggeredTimerParam(void) { return timer[triggeredTimer].param; } static BX_CPP_INLINE void tick1(void) { if (--bx_pc_system.currCountdown == 0) { bx_pc_system.countdownEvent(); } } static BX_CPP_INLINE void tickn(Bit32u n) { while (n >= bx_pc_system.currCountdown) { n -= bx_pc_system.currCountdown; bx_pc_system.currCountdown = 0; bx_pc_system.countdownEvent(); // bx_pc_system.currCountdown is adjusted to new value by countdownevent(). } // 'n' is not (or no longer) >= the countdown size. We can just decrement // the remaining requested ticks and continue. bx_pc_system.currCountdown -= n; } int register_timer_ticks(void* this_ptr, bx_timer_handler_t, Bit64u ticks, bool continuous, bool active, const char *id); void activate_timer_ticks(unsigned index, Bit64u instructions, bool continuous); Bit64u time_usec(); Bit64u time_usec_sequential(); static BX_CPP_INLINE Bit64u time_ticks() { return bx_pc_system.ticksTotal + Bit64u(bx_pc_system.currCountdownPeriod - bx_pc_system.currCountdown); } static BX_CPP_INLINE Bit32u getNumCpuTicksLeftNextEvent(void) { return bx_pc_system.currCountdown; }
This private method is called when the function handling the clock ticks finds that an event has occurred:
void countdownEvent(void);
The Bochs timer implementation requires at least one timer to be active. That's why
there is a so-called nullTimer
to make it work. It is
initialized in the constructor on the first timer slot with the highest possible
timer interval and it's handler is an empty function.
The most important variables of the timer subsystem are initialized on startup
with the nullTimer
values and updated after each timer
modification (register / unregister / activate / deactivate / processing
handler).
ticksTotal: number of ticks total from emulator startup to the last update of timer subsystem
currCountdownPeriod: length of the period from ticksTotal to the next timer event
currCountdown: number of ticks remaining until the next timer event occurs
time_ticks()
method. The number of
useconds since emulator startup is returned with the time_usec()
method computed from the return value of time_ticks()
and
the IPS value.To be continued