#include <sys/time.h>
#include <sys/types.h>
#include <dev/ic/i8253reg.h>
#include <dev/vmm/vmm.h>
#include <event.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "i8253.h"
#include "vmd.h"
#include "atomicio.h"
struct i8253_channel i8253_channel[3];
static struct vm_dev_pipe dev_pipe;
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);
}
}
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);
}
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;
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;
}
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;
}
}
}
}
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) {
if (i8253_channel[2].mode == TIMER_INTTC) {
if (i8253_channel[2].state) {
set_return_data(vei, (1 << 5));
DPRINTF("%s: counter 2 fired, returning "
"0x20", __func__);
} else {
set_return_data(vei, 0);
DPRINTF("%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 {
DPRINTF("%s: discarding data written to PIT misc port",
__func__);
}
return 0xFF;
}
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) {
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);
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;
i8253_channel[sel].last_r = 1;
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].last_w = 0;
i8253_channel[sel].mode = (out_data & 0xe) >> 1;
goto ret;
} else {
log_warnx("i8253 PIT: read from control port "
"unsupported");
set_return_data(vei, 0);
}
} else {
sel = vei->vei.vei_port - (TIMER_CNTR0 + TIMER_BASE);
if (vei->vei.vei_dir == VEI_DIR_OUT) {
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;
clock_gettime(CLOCK_MONOTONIC,
&i8253_channel[sel].ts);
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);
}
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);
}
void
i8253_fire(int fd, short type, void *arg)
{
struct timeval tv;
struct i8253_channel *ctr = (struct i8253_channel *)arg;
vcpu_assert_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;
}
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);
}