summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgrehan <grehan@FreeBSD.org>2013-09-19 04:59:44 +0000
committergrehan <grehan@FreeBSD.org>2013-09-19 04:59:44 +0000
commit88b044ba33a889007ad4cf54a2c0769d777cdd81 (patch)
treeaf8657feae5a53e5c185c48b03daaad8f3132def
parent9239c077f48bfb9780d2363774dc281f97039207 (diff)
downloadFreeBSD-src-88b044ba33a889007ad4cf54a2c0769d777cdd81.zip
FreeBSD-src-88b044ba33a889007ad4cf54a2c0769d777cdd81.tar.gz
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)
-rw-r--r--usr.sbin/bhyve/pit_8254.c106
1 files 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
+#include <sys/types.h>
#include <sys/time.h>
+#include <machine/vmm.h>
#include <machine/clock.h>
-#include <stdio.h>
#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <vmmapi.h>
#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);
OpenPOWER on IntegriCloud