Improve the precision of bhyve's vPIT.

Use 'struct bintime' instead of 'sbintime_t' to manage times in vPIT
to postpone rounding to final results rather than intermediate
results.  In tests performed by Joyent, this reduced the error measured
by Linux guests by 59 ppm.

Smart OS bug:	https://smartos.org/bugview/OS-6923
Submitted by:	Patrick Mooney
Reviewed by:	rgrimes
Obtained from:	SmartOS / Joyent
MFC after:	2 weeks
Differential Revision:	https://reviews.freebsd.org/D20335
This commit is contained in:
John Baldwin 2019-07-20 15:59:49 +00:00
parent 87c8ef55c3
commit 87c39157c6

View File

@ -2,6 +2,7 @@
* Copyright (c) 2014 Tycho Nightingale <tycho.nightingale@pluribusnetworks.com>
* Copyright (c) 2011 NetApp, Inc.
* All rights reserved.
* Copyright (c) 2018 Joyent, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@ -78,7 +79,7 @@ struct vatpit_callout_arg {
struct channel {
int mode;
uint16_t initial; /* initial counter value */
sbintime_t now_sbt; /* uptime when counter was loaded */
struct bintime now_bt; /* uptime when counter was loaded */
uint8_t cr[2];
uint8_t ol[2];
bool slatched; /* status latched */
@ -87,7 +88,7 @@ struct channel {
int olbyte;
int frbyte;
struct callout callout;
sbintime_t callout_sbt; /* target time */
struct bintime callout_bt; /* target time */
struct vatpit_callout_arg callout_arg;
};
@ -95,26 +96,41 @@ struct vatpit {
struct vm *vm;
struct mtx mtx;
sbintime_t freq_sbt;
struct bintime freq_bt;
struct channel channel[3];
};
static void pit_timer_start_cntr0(struct vatpit *vatpit);
static uint64_t
vatpit_delta_ticks(struct vatpit *vatpit, struct channel *c)
{
struct bintime delta;
uint64_t result;
binuptime(&delta);
bintime_sub(&delta, &c->now_bt);
result = delta.sec * PIT_8254_FREQ;
result += delta.frac / vatpit->freq_bt.frac;
return (result);
}
static int
vatpit_get_out(struct vatpit *vatpit, int channel)
{
struct channel *c;
sbintime_t delta_ticks;
uint64_t delta_ticks;
int out;
c = &vatpit->channel[channel];
switch (c->mode) {
case TIMER_INTTC:
delta_ticks = (sbinuptime() - c->now_sbt) / vatpit->freq_sbt;
out = ((c->initial - delta_ticks) <= 0);
delta_ticks = vatpit_delta_ticks(vatpit, c);
out = (delta_ticks >= c->initial);
break;
default:
out = 0;
@ -164,24 +180,28 @@ static void
pit_timer_start_cntr0(struct vatpit *vatpit)
{
struct channel *c;
sbintime_t now, delta, precision;
struct bintime now, delta;
sbintime_t precision;
c = &vatpit->channel[0];
if (c->initial != 0) {
delta = c->initial * vatpit->freq_sbt;
precision = delta >> tc_precexp;
c->callout_sbt = c->callout_sbt + delta;
delta.sec = 0;
delta.frac = vatpit->freq_bt.frac * c->initial;
bintime_add(&c->callout_bt, &delta);
precision = bttosbt(delta) >> tc_precexp;
/*
* Reset 'callout_sbt' if the time that the callout
* Reset 'callout_bt' if the time that the callout
* was supposed to fire is more than 'c->initial'
* ticks in the past.
*/
now = sbinuptime();
if (c->callout_sbt < now)
c->callout_sbt = now + delta;
binuptime(&now);
if (bintime_cmp(&c->callout_bt, &now, <)) {
c->callout_bt = now;
bintime_add(&c->callout_bt, &delta);
}
callout_reset_sbt(&c->callout, c->callout_sbt,
callout_reset_sbt(&c->callout, bttosbt(c->callout_bt),
precision, vatpit_callout_handler, &c->callout_arg,
C_ABSOLUTE);
}
@ -191,7 +211,7 @@ static uint16_t
pit_update_counter(struct vatpit *vatpit, struct channel *c, bool latch)
{
uint16_t lval;
sbintime_t delta_ticks;
uint64_t delta_ticks;
/* cannot latch a new value until the old one has been consumed */
if (latch && c->olbyte != 0)
@ -207,12 +227,11 @@ pit_update_counter(struct vatpit *vatpit, struct channel *c, bool latch)
* here.
*/
c->initial = TIMER_DIV(PIT_8254_FREQ, 100);
c->now_sbt = sbinuptime();
binuptime(&c->now_bt);
c->status &= ~TIMER_STS_NULLCNT;
}
delta_ticks = (sbinuptime() - c->now_sbt) / vatpit->freq_sbt;
delta_ticks = vatpit_delta_ticks(vatpit, c);
lval = c->initial - delta_ticks % c->initial;
if (latch) {
@ -383,10 +402,10 @@ vatpit_handler(struct vm *vm, int vcpuid, bool in, int port, int bytes,
c->frbyte = 0;
c->crbyte = 0;
c->initial = c->cr[0] | (uint16_t)c->cr[1] << 8;
c->now_sbt = sbinuptime();
binuptime(&c->now_bt);
/* Start an interval timer for channel 0 */
if (port == TIMER_CNTR0) {
c->callout_sbt = c->now_sbt;
c->callout_bt = c->now_bt;
pit_timer_start_cntr0(vatpit);
}
if (c->initial == 0)
@ -423,7 +442,6 @@ struct vatpit *
vatpit_init(struct vm *vm)
{
struct vatpit *vatpit;
struct bintime bt;
struct vatpit_callout_arg *arg;
int i;
@ -432,8 +450,7 @@ vatpit_init(struct vm *vm)
mtx_init(&vatpit->mtx, "vatpit lock", NULL, MTX_SPIN);
FREQ2BT(PIT_8254_FREQ, &bt);
vatpit->freq_sbt = bttosbt(bt);
FREQ2BT(PIT_8254_FREQ, &vatpit->freq_bt);
for (i = 0; i < 3; i++) {
callout_init(&vatpit->channel[i].callout, 1);