* Fix high-vs-low counting logic to match the timing diagrams and descriptions in Intel's documentation (23124406.pdf). * Improve reading back the count in mode 3. * Handle GATE input properly with respect to the OUT line, and add a FIXME comment for reading back the counter. GATE is hard wired high for channel 0 (IRQ0), but it can be software controlled on channel 2 (PC speaker).
The output line is now high much more often than before, especially in modes 2 and 4, which might cause updates of the timer chip to generate extra interrupts. But this should only be an issue for some migration cases. Signed-off-by: Matthew Ogilvie <mmogilvi_q...@miniinfo.net> --- There are still some things not quite modeled correctly. See the cover letter of this patch series for details, and the FIXME comments added to the code. hw/i8254.c | 24 ++++++++++++++++++++++-- hw/i8254_common.c | 18 ++++++------------ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/hw/i8254.c b/hw/i8254.c index 77bd5e8..9168016 100644 --- a/hw/i8254.c +++ b/hw/i8254.c @@ -39,6 +39,15 @@ static void pit_irq_timer_update(PITChannelState *s, int64_t current_time); static int pit_get_count(PITChannelState *s) { + /* FIXME: Add some way to represent a paused timer and return + * the paused-at counter value, to better model: + * - gate-triggered modes (1 and 5), + * - gate-pausable modes, + * - [maybe] the "wait until next CLK pulse to load counter" logic, + * - [maybe/not clear] half-loaded counter logic for mode 0, and + * the "null count" status bit, + * - etc. + */ uint64_t d; int counter; @@ -52,8 +61,7 @@ static int pit_get_count(PITChannelState *s) counter = (s->count - d) & 0xffff; break; case 3: - /* XXX: may be incorrect for odd counts */ - counter = s->count - ((2 * d) % s->count); + counter = (s->count - ((2 * d) % s->count)) & 0xfffe; break; default: counter = s->count - (d % s->count); @@ -98,6 +106,18 @@ static inline void pit_load_count(PITChannelState *s, int val) if (val == 0) val = 0x10000; s->count_load_time = qemu_get_clock_ns(vm_clock); + + /* In gate-triggered one-shot modes, indirectly model some pit_get_out() + * cases by setting the load time way back until gate-triggered. + * (Generally only affects reading status from channel 2 speaker, + * due to hard-wired gates on other channels.) + * + * FIXME: This might be redesigned if a paused timer state is added + * for pic_get_count(). + */ + if (s->mode == 1 || s->mode == 5) + s->count_load_time -= muldiv64(val+2, get_ticks_per_sec(), PIT_FREQ); + s->count = val; pit_irq_timer_update(s, s->count_load_time); } diff --git a/hw/i8254_common.c b/hw/i8254_common.c index a03d7cd..dc13c99 100644 --- a/hw/i8254_common.c +++ b/hw/i8254_common.c @@ -50,24 +50,18 @@ int pit_get_out(PITChannelState *s, int64_t current_time) switch (s->mode) { default: case 0: - out = (d >= s->count); - break; case 1: - out = (d < s->count); + out = (d >= s->count); break; case 2: - if ((d % s->count) == 0 && d != 0) { - out = 1; - } else { - out = 0; - } + out = (d % s->count) != (s->count - 1) || s->gate == 0; break; case 3: - out = (d % s->count) < ((s->count + 1) >> 1); + out = (d % s->count) < ((s->count + 1) >> 1) || s->gate == 0; break; case 4: case 5: - out = (d == s->count); + out = (d != s->count); break; } return out; @@ -93,10 +87,10 @@ int64_t pit_get_next_transition_time(PITChannelState *s, int64_t current_time) break; case 2: base = (d / s->count) * s->count; - if ((d - base) == 0 && d != 0) { + if ((d - base) == s->count-1) { next_time = base + s->count; } else { - next_time = base + s->count + 1; + next_time = base + s->count - 1; } break; case 3: -- 1.7.10.2.484.gcd07cc5