From 88b044ba33a889007ad4cf54a2c0769d777cdd81 Mon Sep 17 00:00:00 2001 From: grehan Date: Thu, 19 Sep 2013 04:59:44 +0000 Subject: Implement support for the interrupt-on-terminal-count and s/w-strobe timer modes. These are commonly used by non-FreeBSD o/s's. Approved by: re@ (blanket) --- usr.sbin/bhyve/pit_8254.c | 106 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 16 deletions(-) diff --git a/usr.sbin/bhyve/pit_8254.c b/usr.sbin/bhyve/pit_8254.c index c96596a..44d0cca 100644 --- a/usr.sbin/bhyve/pit_8254.c +++ b/usr.sbin/bhyve/pit_8254.c @@ -29,15 +29,23 @@ #include __FBSDID("$FreeBSD$"); +#include #include +#include #include -#include #include +#include +#include +#include + +#include #include "bhyverun.h" #include "inout.h" +#include "ioapic.h" +#include "mevent.h" #include "pit_8254.h" #define TIMER_SEL_MASK 0xc0 @@ -51,14 +59,19 @@ __FBSDID("$FreeBSD$"); static const int nsecs_per_tick = 1000000000 / PIT_8254_FREQ; struct counter { + struct vmctx *ctx; + struct mevent *tevp; struct timeval tv; /* uptime when counter was loaded */ + int mode; uint16_t initial; /* initial counter value */ uint8_t cr[2]; uint8_t ol[2]; int crbyte; int olbyte; + int frbyte; }; + static void timevalfix(struct timeval *t1) { @@ -82,16 +95,55 @@ timevalsub(struct timeval *t1, const struct timeval *t2) timevalfix(t1); } +static uint64_t pit_mev_count; + +static void +pit_mevent_cb(int fd, enum ev_type type, void *param) +{ + struct counter *c; + + c = param; + + pit_mev_count++; + + ioapic_assert_pin(c->ctx, 0); + ioapic_deassert_pin(c->ctx, 0); + + /* + * Delete the timer for one-shots + */ + if (c->mode != TIMER_RATEGEN) { + mevent_delete(c->tevp); + c->tevp = NULL; + } +} + static void -latch(struct counter *c) +pit_timer_start(struct vmctx *ctx, struct counter *c) +{ + int msecs; + + if (c->initial != 0) { + msecs = c->initial * nsecs_per_tick / 1000000; + if (msecs == 0) + msecs = 1; + + if (c->tevp == NULL) + c->tevp = mevent_add(msecs, EVF_TIMER, pit_mevent_cb, + c); + } +} + +static uint16_t +pit_update_counter(struct counter *c, int latch) { struct timeval tv2; uint16_t lval; uint64_t delta_nsecs, delta_ticks; /* cannot latch a new value until the old one has been consumed */ - if (c->olbyte != 0) - return; + if (latch && c->olbyte != 0) + return (0); if (c->initial == 0 || c->initial == 1) { /* @@ -114,9 +166,14 @@ latch(struct counter *c) delta_ticks = delta_nsecs / nsecs_per_tick; lval = c->initial - delta_ticks % c->initial; - c->olbyte = 2; - c->ol[1] = lval; /* LSB */ - c->ol[0] = lval >> 8; /* MSB */ + + if (latch) { + c->olbyte = 2; + c->ol[1] = lval; /* LSB */ + c->ol[0] = lval >> 8; /* MSB */ + } + + return (lval); } static int @@ -150,13 +207,18 @@ pit_8254_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, * Counter mode is not affected when issuing a * latch command. */ - if (mode != TIMER_RATEGEN && mode != TIMER_SQWAVE) + if (mode != TIMER_INTTC && + mode != TIMER_RATEGEN && + mode != TIMER_SQWAVE && + mode != TIMER_SWSTROBE) return (-1); } c = &counter[sel >> 6]; + c->ctx = ctx; + c->mode = mode; if (rw == TIMER_LATCH) - latch(c); + pit_update_counter(c, 1); else c->olbyte = 0; /* reset latch after reprogramming */ @@ -169,20 +231,32 @@ pit_8254_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, if (in) { /* - * XXX * The spec says that once the output latch is completely - * read it should revert to "following" the counter. We don't - * do this because it is hard and any reasonable OS should - * always latch the counter before trying to read it. + * read it should revert to "following" the counter. Use + * the free running counter for this case (i.e. Linux + * TSC calibration). Assuming the access mode is 16-bit, + * toggle the MSB/LSB bit on each read. */ - if (c->olbyte == 0) - c->olbyte = 2; - *eax = c->ol[--c->olbyte]; + if (c->olbyte == 0) { + uint16_t tmp; + + tmp = pit_update_counter(c, 0); + if (c->frbyte) + tmp >>= 8; + tmp &= 0xff; + *eax = tmp; + c->frbyte ^= 1; + } else + *eax = c->ol[--c->olbyte]; } else { c->cr[c->crbyte++] = *eax; if (c->crbyte == 2) { + c->frbyte = 0; c->crbyte = 0; c->initial = c->cr[0] | (uint16_t)c->cr[1] << 8; + /* Start an interval timer for counter 0 */ + if (port == 0x40) + pit_timer_start(ctx, c); if (c->initial == 0) c->initial = 0xffff; gettimeofday(&c->tv, NULL); -- cgit v1.1