#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/mutex.h>
#include <sys/systm.h>
#include <machine/vmm.h>
#include "vatpic.h"
#include "vioapic.h"
#include "vatpit.h"
#define VATPIT_LOCK(vatpit) mutex_enter(&((vatpit)->lock))
#define VATPIT_UNLOCK(vatpit) mutex_exit(&((vatpit)->lock))
#define VATPIT_LOCKED(vatpit) MUTEX_HELD(&((vatpit)->lock))
#define TIMER_SEL_MASK 0xc0
#define TIMER_RW_MASK 0x30
#define TIMER_MODE_MASK 0x0f
#define TIMER_SEL_READBACK 0xc0
#define TIMER_STS_OUT 0x80
#define TIMER_STS_NULLCNT 0x40
#define VALID_STATUS_BITS (TIMER_STS_OUT | TIMER_STS_NULLCNT)
#define TIMER_RB_LCTR 0x20
#define TIMER_RB_LSTATUS 0x10
#define TIMER_RB_CTR_2 0x08
#define TIMER_RB_CTR_1 0x04
#define TIMER_RB_CTR_0 0x02
#define TMR2_OUT_STS 0x20
#define PIT_8254_FREQ 1193182
#define TIMER_DIV(freq, hz) (((freq) + (hz) / 2) / (hz))
struct vatpit_callout_arg {
struct vatpit *vatpit;
int channel_num;
};
struct channel {
uint8_t mode;
uint16_t initial;
uint8_t reg_cr[2];
uint8_t reg_ol[2];
uint8_t reg_status;
bool slatched;
bool olatched;
bool cr_sel;
bool ol_sel;
bool fr_sel;
hrtime_t time_loaded;
hrtime_t time_target;
uint64_t total_target;
struct callout callout;
struct vatpit_callout_arg callout_arg;
};
struct vatpit {
struct vm *vm;
kmutex_t lock;
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)
{
const hrtime_t delta = gethrtime() - c->time_loaded;
return (hrt_freq_count(delta, PIT_8254_FREQ));
}
static int
vatpit_get_out(struct vatpit *vatpit, int channel)
{
struct channel *c;
uint64_t delta_ticks;
int out;
c = &vatpit->channel[channel];
switch (c->mode) {
case TIMER_INTTC:
delta_ticks = vatpit_delta_ticks(vatpit, c);
out = (delta_ticks >= c->initial);
break;
default:
out = 0;
break;
}
return (out);
}
static void
vatpit_callout_handler(void *a)
{
struct vatpit_callout_arg *arg = a;
struct vatpit *vatpit;
struct callout *callout;
struct channel *c;
vatpit = arg->vatpit;
c = &vatpit->channel[arg->channel_num];
callout = &c->callout;
VATPIT_LOCK(vatpit);
if (callout_pending(callout))
goto done;
if (!callout_active(callout))
goto done;
callout_deactivate(callout);
if (c->mode == TIMER_RATEGEN || c->mode == TIMER_SQWAVE) {
pit_timer_start_cntr0(vatpit);
} else {
c->time_target = 0;
}
(void) vatpic_pulse_irq(vatpit->vm, 0);
(void) vioapic_pulse_irq(vatpit->vm, 2);
done:
VATPIT_UNLOCK(vatpit);
}
static void
vatpit_callout_reset(struct vatpit *vatpit)
{
struct channel *c = &vatpit->channel[0];
ASSERT(VATPIT_LOCKED(vatpit));
callout_reset_hrtime(&c->callout, c->time_target,
vatpit_callout_handler, &c->callout_arg, C_ABSOLUTE);
}
static void
pit_timer_start_cntr0(struct vatpit *vatpit)
{
struct channel *c = &vatpit->channel[0];
if (c->initial == 0) {
return;
}
c->total_target += c->initial;
c->time_target = c->time_loaded +
hrt_freq_interval(PIT_8254_FREQ, c->total_target);
hrtime_t now = gethrtime();
if (c->time_target < now) {
const uint64_t ticks_behind =
hrt_freq_count(now - c->time_target, PIT_8254_FREQ);
c->total_target += roundup(ticks_behind, c->initial);
c->time_target = c->time_loaded +
hrt_freq_interval(PIT_8254_FREQ, c->total_target);
}
vatpit_callout_reset(vatpit);
}
static uint16_t
pit_update_counter(struct vatpit *vatpit, struct channel *c, bool latch)
{
uint16_t lval;
uint64_t delta_ticks;
if (latch && c->olatched)
return (0);
if (c->initial == 0) {
c->initial = TIMER_DIV(PIT_8254_FREQ, 100);
c->time_loaded = gethrtime();
c->reg_status &= ~TIMER_STS_NULLCNT;
}
delta_ticks = vatpit_delta_ticks(vatpit, c);
lval = c->initial - delta_ticks % c->initial;
if (latch) {
c->olatched = true;
c->ol_sel = true;
c->reg_ol[1] = lval;
c->reg_ol[0] = lval >> 8;
}
return (lval);
}
static int
pit_readback1(struct vatpit *vatpit, int channel, uint8_t cmd)
{
struct channel *c;
c = &vatpit->channel[channel];
if ((cmd & TIMER_RB_LCTR) == 0 && !c->olatched) {
(void) pit_update_counter(vatpit, c, true);
}
if ((cmd & TIMER_RB_LSTATUS) == 0 && !c->slatched) {
c->slatched = true;
if (c->mode == TIMER_INTTC && vatpit_get_out(vatpit, channel))
c->reg_status |= TIMER_STS_OUT;
else
c->reg_status &= ~TIMER_STS_OUT;
}
return (0);
}
static int
pit_readback(struct vatpit *vatpit, uint8_t cmd)
{
int error;
error = 0;
if (cmd & TIMER_RB_CTR_0)
error = pit_readback1(vatpit, 0, cmd);
if (!error && cmd & TIMER_RB_CTR_1)
error = pit_readback1(vatpit, 1, cmd);
if (!error && cmd & TIMER_RB_CTR_2)
error = pit_readback1(vatpit, 2, cmd);
return (error);
}
static int
vatpit_update_mode(struct vatpit *vatpit, uint8_t val)
{
struct channel *c;
int sel, rw;
uint8_t mode;
sel = val & TIMER_SEL_MASK;
rw = val & TIMER_RW_MASK;
mode = val & TIMER_MODE_MASK;
if ((mode & TIMER_RATEGEN) != 0) {
mode &= ~TIMER_SWSTROBE;
}
if (sel == TIMER_SEL_READBACK)
return (pit_readback(vatpit, val));
if (rw != TIMER_LATCH && rw != TIMER_16BIT)
return (-1);
if (rw != TIMER_LATCH) {
if (mode != TIMER_INTTC &&
mode != TIMER_RATEGEN &&
mode != TIMER_SQWAVE &&
mode != TIMER_SWSTROBE)
return (-1);
}
c = &vatpit->channel[sel >> 6];
if (rw == TIMER_LATCH) {
(void) pit_update_counter(vatpit, c, true);
} else {
c->mode = mode;
c->olatched = false;
c->reg_status |= TIMER_STS_NULLCNT;
}
return (0);
}
int
vatpit_handler(void *arg, bool in, uint16_t port, uint8_t bytes, uint32_t *eax)
{
struct vatpit *vatpit = arg;
struct channel *c;
uint8_t val;
int error;
if (bytes != 1)
return (-1);
val = *eax;
if (port == TIMER_MODE) {
if (in) {
return (-1);
}
VATPIT_LOCK(vatpit);
error = vatpit_update_mode(vatpit, val);
VATPIT_UNLOCK(vatpit);
return (error);
}
KASSERT(port >= TIMER_CNTR0 && port <= TIMER_CNTR2,
("invalid port 0x%x", port));
c = &vatpit->channel[port - TIMER_CNTR0];
VATPIT_LOCK(vatpit);
if (in && c->slatched) {
*eax = c->reg_status;
c->slatched = false;
c->reg_status = 0;
} else if (in) {
if (!c->olatched) {
uint16_t tmp;
tmp = pit_update_counter(vatpit, c, false);
if (c->fr_sel) {
tmp >>= 8;
}
tmp &= 0xff;
*eax = tmp;
c->fr_sel = !c->fr_sel;
} else {
if (c->ol_sel) {
*eax = c->reg_ol[1];
c->ol_sel = false;
} else {
*eax = c->reg_ol[0];
c->olatched = false;
}
}
} else {
if (!c->cr_sel) {
c->reg_cr[0] = *eax;
c->cr_sel = true;
} else {
c->reg_cr[1] = *eax;
c->cr_sel = false;
c->reg_status &= ~TIMER_STS_NULLCNT;
c->fr_sel = false;
c->initial = c->reg_cr[0] | (uint16_t)c->reg_cr[1] << 8;
c->time_loaded = gethrtime();
if (port == TIMER_CNTR0) {
c->time_target = c->time_loaded;
c->total_target = 0;
pit_timer_start_cntr0(vatpit);
}
if (c->initial == 0)
c->initial = 0xffff;
}
}
VATPIT_UNLOCK(vatpit);
return (0);
}
int
vatpit_nmisc_handler(void *arg, bool in, uint16_t port, uint8_t bytes,
uint32_t *eax)
{
struct vatpit *vatpit = arg;
if (in) {
VATPIT_LOCK(vatpit);
if (vatpit_get_out(vatpit, 2))
*eax = TMR2_OUT_STS;
else
*eax = 0;
VATPIT_UNLOCK(vatpit);
}
return (0);
}
struct vatpit *
vatpit_init(struct vm *vm)
{
struct vatpit *vatpit;
struct vatpit_callout_arg *arg;
int i;
vatpit = kmem_zalloc(sizeof (struct vatpit), KM_SLEEP);
vatpit->vm = vm;
mutex_init(&vatpit->lock, NULL, MUTEX_ADAPTIVE, NULL);
for (i = 0; i < 3; i++) {
callout_init(&vatpit->channel[i].callout, 1);
arg = &vatpit->channel[i].callout_arg;
arg->vatpit = vatpit;
arg->channel_num = i;
}
return (vatpit);
}
void
vatpit_cleanup(struct vatpit *vatpit)
{
int i;
for (i = 0; i < 3; i++)
callout_drain(&vatpit->channel[i].callout);
mutex_destroy(&vatpit->lock);
kmem_free(vatpit, sizeof (*vatpit));
}
void
vatpit_localize_resources(struct vatpit *vatpit)
{
for (uint_t i = 0; i < 3; i++) {
if (vatpit->channel[i].mode != 0) {
vmm_glue_callout_localize(&vatpit->channel[i].callout);
}
}
}
void
vatpit_pause(struct vatpit *vatpit)
{
struct channel *c = &vatpit->channel[0];
VATPIT_LOCK(vatpit);
callout_stop(&c->callout);
VATPIT_UNLOCK(vatpit);
}
void
vatpit_resume(struct vatpit *vatpit)
{
struct channel *c = &vatpit->channel[0];
VATPIT_LOCK(vatpit);
ASSERT(!callout_active(&c->callout));
if (c->time_target != 0) {
vatpit_callout_reset(vatpit);
}
VATPIT_UNLOCK(vatpit);
}
static int
vatpit_data_read(void *datap, const vmm_data_req_t *req)
{
VERIFY3U(req->vdr_class, ==, VDC_ATPIT);
VERIFY3U(req->vdr_version, ==, 1);
VERIFY3U(req->vdr_len, >=, sizeof (struct vdi_atpit_v1));
struct vatpit *vatpit = datap;
struct vdi_atpit_v1 *out = req->vdr_data;
VATPIT_LOCK(vatpit);
for (uint_t i = 0; i < 3; i++) {
const struct channel *src = &vatpit->channel[i];
struct vdi_atpit_channel_v1 *chan = &out->va_channel[i];
chan->vac_initial = src->initial;
chan->vac_reg_cr =
(src->reg_cr[0] | (uint16_t)src->reg_cr[1] << 8);
chan->vac_reg_ol =
(src->reg_ol[0] | (uint16_t)src->reg_ol[1] << 8);
chan->vac_reg_status = src->reg_status;
chan->vac_mode = src->mode;
chan->vac_status =
(src->slatched ? (1 << 0) : 0) |
(src->olatched ? (1 << 1) : 0) |
(src->cr_sel ? (1 << 2) : 0) |
(src->ol_sel ? (1 << 3) : 0) |
(src->fr_sel ? (1 << 4) : 0);
if (i == 0 && src->time_target != 0) {
chan->vac_time_target =
vm_normalize_hrtime(vatpit->vm, src->time_target);
} else {
chan->vac_time_target = 0;
}
}
VATPIT_UNLOCK(vatpit);
return (0);
}
static bool
vatpit_data_validate(const struct vdi_atpit_v1 *src)
{
for (uint_t i = 0; i < 3; i++) {
const struct vdi_atpit_channel_v1 *chan = &src->va_channel[i];
if ((chan->vac_status & ~VALID_STATUS_BITS) != 0) {
return (false);
}
}
return (true);
}
static int
vatpit_data_write(void *datap, const vmm_data_req_t *req)
{
VERIFY3U(req->vdr_class, ==, VDC_ATPIT);
VERIFY3U(req->vdr_version, ==, 1);
VERIFY3U(req->vdr_len, >=, sizeof (struct vdi_atpit_v1));
struct vatpit *vatpit = datap;
const struct vdi_atpit_v1 *src = req->vdr_data;
if (!vatpit_data_validate(src)) {
return (EINVAL);
}
VATPIT_LOCK(vatpit);
for (uint_t i = 0; i < 3; i++) {
const struct vdi_atpit_channel_v1 *chan = &src->va_channel[i];
struct channel *out = &vatpit->channel[i];
out->initial = chan->vac_initial;
out->reg_cr[0] = chan->vac_reg_cr;
out->reg_cr[1] = chan->vac_reg_cr >> 8;
out->reg_ol[0] = chan->vac_reg_ol;
out->reg_ol[1] = chan->vac_reg_ol >> 8;
out->reg_status = chan->vac_reg_status;
out->mode = chan->vac_mode;
out->slatched = (chan->vac_status & (1 << 0)) != 0;
out->olatched = (chan->vac_status & (1 << 1)) != 0;
out->cr_sel = (chan->vac_status & (1 << 2)) != 0;
out->ol_sel = (chan->vac_status & (1 << 3)) != 0;
out->fr_sel = (chan->vac_status & (1 << 4)) != 0;
if (i != 0) {
continue;
}
struct callout *callout = &out->callout;
if (callout_active(callout)) {
callout_deactivate(callout);
}
if (chan->vac_time_target == 0) {
out->time_loaded = 0;
out->time_target = 0;
continue;
}
const uint64_t time_target =
vm_denormalize_hrtime(vatpit->vm, chan->vac_time_target);
out->total_target = out->initial;
out->time_target = time_target;
out->time_loaded = time_target -
hrt_freq_interval(PIT_8254_FREQ, out->initial);
if (!vm_is_paused(vatpit->vm)) {
vatpit_callout_reset(vatpit);
}
}
VATPIT_UNLOCK(vatpit);
return (0);
}
static const vmm_data_version_entry_t atpit_v1 = {
.vdve_class = VDC_ATPIT,
.vdve_version = 1,
.vdve_len_expect = sizeof (struct vdi_atpit_v1),
.vdve_readf = vatpit_data_read,
.vdve_writef = vatpit_data_write,
};
VMM_DATA_VERSION(atpit_v1);