File: [local] / src / usr.sbin / vmd / i8253.c (download)
Revision 1.39, Fri Feb 9 14:35:47 2024 UTC (3 months, 3 weeks ago) by dv
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD Changes since 1.38: +3 -3 lines
Tuck vmd's i8253 reset debug logs behind DPRINTF.
It's super chatty and pollutes verbose logging.
|
/* $OpenBSD: i8253.c,v 1.39 2024/02/09 14:35:47 dv Exp $ */
/*
* Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/time.h>
#include <sys/types.h>
#include <dev/ic/i8253reg.h>
#include <machine/vmmvar.h>
#include <event.h>
#include <string.h>
#include <stddef.h>
#include <time.h>
#include <unistd.h>
#include "i8253.h"
#include "vmd.h"
#include "vmm.h"
#include "atomicio.h"
extern char *__progname;
/*
* Channel 0 is used to generate the legacy hardclock interrupt (HZ).
* Channels 1 and 2 can be used by the guest OS as regular timers,
* but channel 2 is not connected to any pcppi(4)-like device. Like
* a regular PC, channel 2 status can also be read from port 0x61.
*/
struct i8253_channel i8253_channel[3];
static struct vm_dev_pipe dev_pipe;
/*
* i8253_pipe_dispatch
*
* Reads a message off the pipe, expecting one that corresponds to a
* reset request for a specific channel.
*/
static void
i8253_pipe_dispatch(int fd, short event, void *arg)
{
enum pipe_msg_type msg;
msg = vm_pipe_recv(&dev_pipe);
switch (msg) {
case I8253_RESET_CHAN_0:
i8253_reset(0);
break;
case I8253_RESET_CHAN_1:
i8253_reset(1);
break;
case I8253_RESET_CHAN_2:
i8253_reset(2);
break;
default:
fatalx("%s: unexpected pipe message %d", __func__, msg);
}
}
/*
* i8253_init
*
* Initialize the emulated i8253 PIT.
*
* Parameters:
* vm_id: vmm(4)-assigned ID of the VM
*/
void
i8253_init(uint32_t vm_id)
{
memset(&i8253_channel, 0, sizeof(struct i8253_channel));
clock_gettime(CLOCK_MONOTONIC, &i8253_channel[0].ts);
i8253_channel[0].start = 0xFFFF;
i8253_channel[0].mode = TIMER_INTTC;
i8253_channel[0].last_r = 1;
i8253_channel[0].vm_id = vm_id;
i8253_channel[0].state = 0;
i8253_channel[1].start = 0xFFFF;
i8253_channel[1].mode = TIMER_INTTC;
i8253_channel[1].last_r = 1;
i8253_channel[1].vm_id = vm_id;
i8253_channel[1].state = 0;
i8253_channel[2].start = 0xFFFF;
i8253_channel[2].mode = TIMER_INTTC;
i8253_channel[2].last_r = 1;
i8253_channel[2].vm_id = vm_id;
i8253_channel[2].state = 0;
evtimer_set(&i8253_channel[0].timer, i8253_fire, &i8253_channel[0]);
evtimer_set(&i8253_channel[1].timer, i8253_fire, &i8253_channel[1]);
evtimer_set(&i8253_channel[2].timer, i8253_fire, &i8253_channel[2]);
vm_pipe_init(&dev_pipe, i8253_pipe_dispatch);
event_add(&dev_pipe.read_ev, NULL);
}
/*
* i8253_do_readback
*
* Handles the readback status command. The readback status command latches
* the current counter value plus various status bits.
*
* Parameters:
* data: The command word written by the guest VM
*/
void
i8253_do_readback(uint32_t data)
{
struct timespec now, delta;
uint64_t ns, ticks;
int readback_channel[3] = { TIMER_RB_C0, TIMER_RB_C1, TIMER_RB_C2 };
int i;
/* bits are inverted here - !TIMER_RB_STATUS == enable chan readback */
if (data & ~TIMER_RB_STATUS) {
i8253_channel[0].rbs = (data & TIMER_RB_C0) ? 1 : 0;
i8253_channel[1].rbs = (data & TIMER_RB_C1) ? 1 : 0;
i8253_channel[2].rbs = (data & TIMER_RB_C2) ? 1 : 0;
}
/* !TIMER_RB_COUNT == enable counter readback */
if (data & ~TIMER_RB_COUNT) {
clock_gettime(CLOCK_MONOTONIC, &now);
for (i = 0; i < 3; i++) {
if (data & readback_channel[i]) {
timespecsub(&now, &i8253_channel[i].ts, &delta);
ns = delta.tv_sec * 1000000000 + delta.tv_nsec;
ticks = ns / NS_PER_TICK;
if (i8253_channel[i].start)
i8253_channel[i].olatch =
i8253_channel[i].start -
ticks % i8253_channel[i].start;
else
i8253_channel[i].olatch = 0;
}
}
}
}
/*
* vcpu_exit_i8253_misc
*
* Handles the 0x61 misc i8253 PIT register in/out exits.
*
* Parameters:
* vrp: vm run parameters containing exit information for the I/O
* instruction being performed
*
* Return value:
* Always 0xFF (no interrupt should be injected)
*/
uint8_t
vcpu_exit_i8253_misc(struct vm_run_params *vrp)
{
struct vm_exit *vei = vrp->vrp_exit;
uint16_t cur;
uint64_t ns, ticks;
struct timespec now, delta;
if (vei->vei.vei_dir == VEI_DIR_IN) {
/* Port 0x61[5] = counter channel 2 state */
if (i8253_channel[2].mode == TIMER_INTTC) {
if (i8253_channel[2].state) {
set_return_data(vei, (1 << 5));
log_debug("%s: counter 2 fired, returning "
"0x20", __func__);
} else {
set_return_data(vei, 0);
log_debug("%s: counter 2 clear, returning 0x0",
__func__);
}
} else if (i8253_channel[2].mode == TIMER_SQWAVE) {
clock_gettime(CLOCK_MONOTONIC, &now);
timespecsub(&now, &i8253_channel[2].ts, &delta);
ns = delta.tv_sec * 1000000000 + delta.tv_nsec;
ticks = ns / NS_PER_TICK;
if (i8253_channel[2].start) {
cur = i8253_channel[2].start -
ticks % i8253_channel[2].start;
if (cur > i8253_channel[2].start / 2)
set_return_data(vei, 1);
else
set_return_data(vei, 0);
}
}
} else {
log_debug("%s: discarding data written to PIT misc port",
__func__);
}
return 0xFF;
}
/*
* vcpu_exit_i8253
*
* Handles emulated i8253 PIT access (in/out instruction to PIT ports).
*
* Parameters:
* vrp: vm run parameters containing exit information for the I/O
* instruction being performed
*
* Return value:
* Interrupt to inject to the guest VM, or 0xFF if no interrupt should
* be injected.
*/
uint8_t
vcpu_exit_i8253(struct vm_run_params *vrp)
{
uint32_t out_data = 0;
uint8_t sel, rw, data;
uint64_t ns, ticks;
struct timespec now, delta;
struct vm_exit *vei = vrp->vrp_exit;
get_input_data(vei, &out_data);
if (vei->vei.vei_port == TIMER_CTRL) {
if (vei->vei.vei_dir == VEI_DIR_OUT) { /* OUT instruction */
sel = out_data &
(TIMER_SEL0 | TIMER_SEL1 | TIMER_SEL2);
sel = sel >> 6;
if (sel == 3) {
i8253_do_readback(out_data);
return (0xFF);
}
rw = out_data & (TIMER_LATCH | TIMER_16BIT);
/*
* Since we don't truly emulate each tick of the PIT
* counter, when the guest asks for the timer to be
* latched, simulate what the counter would have been
* had we performed full emulation. We do this by
* calculating when the counter was reset vs how much
* time has elapsed, then bias by the counter tick
* rate.
*/
if (rw == TIMER_LATCH) {
clock_gettime(CLOCK_MONOTONIC, &now);
timespecsub(&now, &i8253_channel[sel].ts,
&delta);
ns = delta.tv_sec * 1000000000 + delta.tv_nsec;
ticks = ns / NS_PER_TICK;
if (i8253_channel[sel].start) {
i8253_channel[sel].olatch =
i8253_channel[sel].start -
ticks % i8253_channel[sel].start;
} else
i8253_channel[sel].olatch = 0;
goto ret;
} else if (rw != TIMER_16BIT) {
log_warnx("%s: i8253 PIT: unsupported counter "
"%d rw mode 0x%x selected", __func__,
sel, (rw & TIMER_16BIT));
}
i8253_channel[sel].mode = (out_data & 0xe) >> 1;
goto ret;
} else {
log_warnx("%s: i8253 PIT: read from control port "
"unsupported", __progname);
set_return_data(vei, 0);
}
} else {
sel = vei->vei.vei_port - (TIMER_CNTR0 + TIMER_BASE);
if (vei->vei.vei_dir == VEI_DIR_OUT) { /* OUT instruction */
if (i8253_channel[sel].last_w == 0) {
i8253_channel[sel].ilatch |= (out_data & 0xff);
i8253_channel[sel].last_w = 1;
} else {
i8253_channel[sel].ilatch |=
((out_data & 0xff) << 8);
i8253_channel[sel].start =
i8253_channel[sel].ilatch;
i8253_channel[sel].last_w = 0;
if (i8253_channel[sel].start == 0)
i8253_channel[sel].start = 0xffff;
DPRINTF("%s: channel %d reset, mode=%d, "
"start=%d\n", __func__,
sel, i8253_channel[sel].mode,
i8253_channel[sel].start);
vm_pipe_send(&dev_pipe, sel);
}
} else {
if (i8253_channel[sel].rbs) {
i8253_channel[sel].rbs = 0;
data = i8253_channel[sel].mode << 1;
data |= TIMER_16BIT;
set_return_data(vei, data);
goto ret;
}
if (i8253_channel[sel].last_r == 0) {
data = i8253_channel[sel].olatch >> 8;
set_return_data(vei, data);
i8253_channel[sel].last_r = 1;
} else {
data = i8253_channel[sel].olatch & 0xFF;
set_return_data(vei, data);
i8253_channel[sel].last_r = 0;
}
}
}
ret:
return (0xFF);
}
/*
* i8253_reset
*
* Resets the i8253's counter timer
*
* Parameters:
* chn: counter ID. Only channel ID 0 is presently emulated.
*/
void
i8253_reset(uint8_t chn)
{
struct timeval tv;
evtimer_del(&i8253_channel[chn].timer);
timerclear(&tv);
i8253_channel[chn].in_use = 1;
i8253_channel[chn].state = 0;
tv.tv_usec = (i8253_channel[chn].start * NS_PER_TICK) / 1000;
clock_gettime(CLOCK_MONOTONIC, &i8253_channel[chn].ts);
evtimer_add(&i8253_channel[chn].timer, &tv);
}
/*
* i8253_fire
*
* Callback invoked when the 8253 PIT timer fires. This will assert
* IRQ0 on the legacy PIC attached to VCPU0.
*
* Parameters:
* fd: unused
* type: unused
* arg: VM ID
*/
void
i8253_fire(int fd, short type, void *arg)
{
struct timeval tv;
struct i8253_channel *ctr = (struct i8253_channel *)arg;
vcpu_assert_pic_irq(ctr->vm_id, 0, 0);
if (ctr->mode != TIMER_INTTC) {
timerclear(&tv);
tv.tv_usec = (ctr->start * NS_PER_TICK) / 1000;
evtimer_add(&ctr->timer, &tv);
} else
ctr->state = 1;
}
int
i8253_dump(int fd)
{
log_debug("%s: sending PIT", __func__);
if (atomicio(vwrite, fd, &i8253_channel, sizeof(i8253_channel)) !=
sizeof(i8253_channel)) {
log_warnx("%s: error writing PIT to fd", __func__);
return (-1);
}
return (0);
}
int
i8253_restore(int fd, uint32_t vm_id)
{
int i;
log_debug("%s: restoring PIT", __func__);
if (atomicio(read, fd, &i8253_channel, sizeof(i8253_channel)) !=
sizeof(i8253_channel)) {
log_warnx("%s: error reading PIT from fd", __func__);
return (-1);
}
for (i = 0; i < 3; i++) {
memset(&i8253_channel[i].timer, 0, sizeof(struct event));
i8253_channel[i].vm_id = vm_id;
evtimer_set(&i8253_channel[i].timer, i8253_fire,
&i8253_channel[i]);
i8253_reset(i);
}
vm_pipe_init(&dev_pipe, i8253_pipe_dispatch);
return (0);
}
void
i8253_stop(void)
{
int i;
for (i = 0; i < 3; i++)
evtimer_del(&i8253_channel[i].timer);
event_del(&dev_pipe.read_ev);
}
void
i8253_start(void)
{
int i;
for (i = 0; i < 3; i++)
if (i8253_channel[i].in_use)
i8253_reset(i);
event_add(&dev_pipe.read_ev, NULL);
}