#include <sys/cdefs.h>
#include "opt_bhyve_snapshot.h"
#include <sys/param.h>
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/systm.h>
#include <x86/apicreg.h>
#include <machine/vmm.h>
#include <machine/vmm_snapshot.h>
#include <dev/ic/i8259.h>
#include <dev/vmm/vmm_ktr.h>
#include "vmm_lapic.h"
#include "vioapic.h"
#include "vatpic.h"
static MALLOC_DEFINE(M_VATPIC, "atpic", "bhyve virtual atpic (8259)");
#define VATPIC_LOCK(vatpic) mtx_lock_spin(&((vatpic)->mtx))
#define VATPIC_UNLOCK(vatpic) mtx_unlock_spin(&((vatpic)->mtx))
#define VATPIC_LOCKED(vatpic) mtx_owned(&((vatpic)->mtx))
enum irqstate {
IRQSTATE_ASSERT,
IRQSTATE_DEASSERT,
IRQSTATE_PULSE
};
struct atpic {
bool ready;
int icw_num;
int rd_cmd_reg;
bool aeoi;
bool poll;
bool rotate;
bool sfn;
int irq_base;
uint8_t request;
uint8_t service;
uint8_t mask;
uint8_t smm;
int acnt[8];
int lowprio;
bool intr_raised;
};
struct vatpic {
struct vm *vm;
struct mtx mtx;
struct atpic atpic[2];
uint8_t elc[2];
};
#define VATPIC_CTR0(vatpic, fmt) \
VM_CTR0((vatpic)->vm, fmt)
#define VATPIC_CTR1(vatpic, fmt, a1) \
VM_CTR1((vatpic)->vm, fmt, a1)
#define VATPIC_CTR2(vatpic, fmt, a1, a2) \
VM_CTR2((vatpic)->vm, fmt, a1, a2)
#define VATPIC_CTR3(vatpic, fmt, a1, a2, a3) \
VM_CTR3((vatpic)->vm, fmt, a1, a2, a3)
#define VATPIC_CTR4(vatpic, fmt, a1, a2, a3, a4) \
VM_CTR4((vatpic)->vm, fmt, a1, a2, a3, a4)
#define ATPIC_PIN_FOREACH(pinvar, atpic, tmpvar) \
for (tmpvar = 0, pinvar = (atpic->lowprio + 1) & 0x7; \
tmpvar < 8; \
tmpvar++, pinvar = (pinvar + 1) & 0x7)
static void vatpic_set_pinstate(struct vatpic *vatpic, int pin, bool newstate);
static __inline bool
master_atpic(struct vatpic *vatpic, struct atpic *atpic)
{
if (atpic == &vatpic->atpic[0])
return (true);
else
return (false);
}
static __inline int
vatpic_get_highest_isrpin(struct atpic *atpic)
{
int bit, pin;
int i;
ATPIC_PIN_FOREACH(pin, atpic, i) {
bit = (1 << pin);
if (atpic->service & bit) {
if (atpic->smm && (atpic->mask & bit) != 0)
continue;
else
return (pin);
}
}
return (-1);
}
static __inline int
vatpic_get_highest_irrpin(struct atpic *atpic)
{
int serviced;
int bit, pin, tmp;
serviced = atpic->service;
if (atpic->sfn)
serviced &= ~(1 << 2);
if (atpic->smm)
serviced = 0;
ATPIC_PIN_FOREACH(pin, atpic, tmp) {
bit = 1 << pin;
if ((serviced & bit) != 0)
break;
if ((atpic->request & bit) != 0 && (atpic->mask & bit) == 0)
return (pin);
}
return (-1);
}
static void
vatpic_notify_intr(struct vatpic *vatpic)
{
struct atpic *atpic;
int pin;
KASSERT(VATPIC_LOCKED(vatpic), ("vatpic_notify_intr not locked"));
atpic = &vatpic->atpic[1];
if (!atpic->intr_raised &&
(pin = vatpic_get_highest_irrpin(atpic)) != -1) {
VATPIC_CTR4(vatpic, "atpic slave notify pin = %d "
"(imr 0x%x irr 0x%x isr 0x%x)", pin,
atpic->mask, atpic->request, atpic->service);
atpic->intr_raised = true;
vatpic_set_pinstate(vatpic, 2, true);
vatpic_set_pinstate(vatpic, 2, false);
} else {
VATPIC_CTR3(vatpic, "atpic slave no eligible interrupts "
"(imr 0x%x irr 0x%x isr 0x%x)",
atpic->mask, atpic->request, atpic->service);
}
atpic = &vatpic->atpic[0];
if (!atpic->intr_raised &&
(pin = vatpic_get_highest_irrpin(atpic)) != -1) {
VATPIC_CTR4(vatpic, "atpic master notify pin = %d "
"(imr 0x%x irr 0x%x isr 0x%x)", pin,
atpic->mask, atpic->request, atpic->service);
atpic->intr_raised = true;
lapic_set_local_intr(vatpic->vm, NULL, APIC_LVT_LINT0);
vioapic_pulse_irq(vatpic->vm, 0);
} else {
VATPIC_CTR3(vatpic, "atpic master no eligible interrupts "
"(imr 0x%x irr 0x%x isr 0x%x)",
atpic->mask, atpic->request, atpic->service);
}
}
static int
vatpic_icw1(struct vatpic *vatpic, struct atpic *atpic, uint8_t val)
{
VATPIC_CTR1(vatpic, "atpic icw1 0x%x", val);
atpic->ready = false;
atpic->icw_num = 1;
atpic->request = 0;
atpic->mask = 0;
atpic->lowprio = 7;
atpic->rd_cmd_reg = 0;
atpic->poll = 0;
atpic->smm = 0;
if ((val & ICW1_SNGL) != 0) {
VATPIC_CTR0(vatpic, "vatpic cascade mode required");
return (-1);
}
if ((val & ICW1_IC4) == 0) {
VATPIC_CTR0(vatpic, "vatpic icw4 required");
return (-1);
}
atpic->icw_num++;
return (0);
}
static int
vatpic_icw2(struct vatpic *vatpic, struct atpic *atpic, uint8_t val)
{
VATPIC_CTR1(vatpic, "atpic icw2 0x%x", val);
atpic->irq_base = val & 0xf8;
atpic->icw_num++;
return (0);
}
static int
vatpic_icw3(struct vatpic *vatpic, struct atpic *atpic, uint8_t val)
{
VATPIC_CTR1(vatpic, "atpic icw3 0x%x", val);
atpic->icw_num++;
return (0);
}
static int
vatpic_icw4(struct vatpic *vatpic, struct atpic *atpic, uint8_t val)
{
VATPIC_CTR1(vatpic, "atpic icw4 0x%x", val);
if ((val & ICW4_8086) == 0) {
VATPIC_CTR0(vatpic, "vatpic microprocessor mode required");
return (-1);
}
if ((val & ICW4_AEOI) != 0)
atpic->aeoi = true;
if ((val & ICW4_SFNM) != 0) {
if (master_atpic(vatpic, atpic)) {
atpic->sfn = true;
} else {
VATPIC_CTR1(vatpic, "Ignoring special fully nested "
"mode on slave atpic: %#x", val);
}
}
atpic->icw_num = 0;
atpic->ready = true;
return (0);
}
static int
vatpic_ocw1(struct vatpic *vatpic, struct atpic *atpic, uint8_t val)
{
VATPIC_CTR1(vatpic, "atpic ocw1 0x%x", val);
atpic->mask = val & 0xff;
return (0);
}
static int
vatpic_ocw2(struct vatpic *vatpic, struct atpic *atpic, uint8_t val)
{
VATPIC_CTR1(vatpic, "atpic ocw2 0x%x", val);
atpic->rotate = ((val & OCW2_R) != 0);
if ((val & OCW2_EOI) != 0) {
int isr_bit;
if ((val & OCW2_SL) != 0) {
isr_bit = val & 0x7;
} else {
isr_bit = vatpic_get_highest_isrpin(atpic);
}
if (isr_bit != -1) {
atpic->service &= ~(1 << isr_bit);
if (atpic->rotate)
atpic->lowprio = isr_bit;
}
} else if ((val & OCW2_SL) != 0 && atpic->rotate == true) {
atpic->lowprio = val & 0x7;
}
return (0);
}
static int
vatpic_ocw3(struct vatpic *vatpic, struct atpic *atpic, uint8_t val)
{
VATPIC_CTR1(vatpic, "atpic ocw3 0x%x", val);
if (val & OCW3_ESMM) {
atpic->smm = val & OCW3_SMM ? 1 : 0;
VATPIC_CTR2(vatpic, "%s atpic special mask mode %s",
master_atpic(vatpic, atpic) ? "master" : "slave",
atpic->smm ? "enabled" : "disabled");
}
if (val & OCW3_RR) {
atpic->rd_cmd_reg = val & OCW3_RIS;
atpic->poll = ((val & OCW3_P) != 0);
}
return (0);
}
static void
vatpic_set_pinstate(struct vatpic *vatpic, int pin, bool newstate)
{
struct atpic *atpic;
int oldcnt, newcnt;
bool level;
KASSERT(pin >= 0 && pin < 16,
("vatpic_set_pinstate: invalid pin number %d", pin));
KASSERT(VATPIC_LOCKED(vatpic),
("vatpic_set_pinstate: vatpic is not locked"));
atpic = &vatpic->atpic[pin >> 3];
oldcnt = atpic->acnt[pin & 0x7];
if (newstate)
atpic->acnt[pin & 0x7]++;
else
atpic->acnt[pin & 0x7]--;
newcnt = atpic->acnt[pin & 0x7];
if (newcnt < 0) {
VATPIC_CTR2(vatpic, "atpic pin%d: bad acnt %d", pin, newcnt);
}
level = ((vatpic->elc[pin >> 3] & (1 << (pin & 0x7))) != 0);
if ((oldcnt == 0 && newcnt == 1) || (newcnt > 0 && level == true)) {
VATPIC_CTR1(vatpic, "atpic pin%d: asserted", pin);
atpic->request |= (1 << (pin & 0x7));
} else if (oldcnt == 1 && newcnt == 0) {
VATPIC_CTR1(vatpic, "atpic pin%d: deasserted", pin);
if (level)
atpic->request &= ~(1 << (pin & 0x7));
} else {
VATPIC_CTR3(vatpic, "atpic pin%d: %s, ignored, acnt %d",
pin, newstate ? "asserted" : "deasserted", newcnt);
}
vatpic_notify_intr(vatpic);
}
static int
vatpic_set_irqstate(struct vm *vm, int irq, enum irqstate irqstate)
{
struct vatpic *vatpic;
struct atpic *atpic;
if (irq < 0 || irq > 15)
return (EINVAL);
vatpic = vm_atpic(vm);
atpic = &vatpic->atpic[irq >> 3];
if (atpic->ready == false)
return (0);
VATPIC_LOCK(vatpic);
switch (irqstate) {
case IRQSTATE_ASSERT:
vatpic_set_pinstate(vatpic, irq, true);
break;
case IRQSTATE_DEASSERT:
vatpic_set_pinstate(vatpic, irq, false);
break;
case IRQSTATE_PULSE:
vatpic_set_pinstate(vatpic, irq, true);
vatpic_set_pinstate(vatpic, irq, false);
break;
default:
panic("vatpic_set_irqstate: invalid irqstate %d", irqstate);
}
VATPIC_UNLOCK(vatpic);
return (0);
}
int
vatpic_assert_irq(struct vm *vm, int irq)
{
return (vatpic_set_irqstate(vm, irq, IRQSTATE_ASSERT));
}
int
vatpic_deassert_irq(struct vm *vm, int irq)
{
return (vatpic_set_irqstate(vm, irq, IRQSTATE_DEASSERT));
}
int
vatpic_pulse_irq(struct vm *vm, int irq)
{
return (vatpic_set_irqstate(vm, irq, IRQSTATE_PULSE));
}
int
vatpic_set_irq_trigger(struct vm *vm, int irq, enum vm_intr_trigger trigger)
{
struct vatpic *vatpic;
if (irq < 0 || irq > 15)
return (EINVAL);
if (trigger == LEVEL_TRIGGER) {
switch (irq) {
case 0:
case 1:
case 2:
case 8:
case 13:
return (EINVAL);
}
}
vatpic = vm_atpic(vm);
VATPIC_LOCK(vatpic);
if (trigger == LEVEL_TRIGGER)
vatpic->elc[irq >> 3] |= 1 << (irq & 0x7);
else
vatpic->elc[irq >> 3] &= ~(1 << (irq & 0x7));
VATPIC_UNLOCK(vatpic);
return (0);
}
void
vatpic_pending_intr(struct vm *vm, int *vecptr)
{
struct vatpic *vatpic;
struct atpic *atpic;
int pin;
vatpic = vm_atpic(vm);
atpic = &vatpic->atpic[0];
VATPIC_LOCK(vatpic);
pin = vatpic_get_highest_irrpin(atpic);
if (pin == 2) {
atpic = &vatpic->atpic[1];
pin = vatpic_get_highest_irrpin(atpic);
}
if (pin == -1)
pin = 7;
KASSERT(pin >= 0 && pin <= 7, ("%s: invalid pin %d", __func__, pin));
*vecptr = atpic->irq_base + pin;
VATPIC_UNLOCK(vatpic);
}
static void
vatpic_pin_accepted(struct atpic *atpic, int pin)
{
atpic->intr_raised = false;
if (atpic->acnt[pin] == 0)
atpic->request &= ~(1 << pin);
if (atpic->aeoi == true) {
if (atpic->rotate == true)
atpic->lowprio = pin;
} else {
atpic->service |= (1 << pin);
}
}
void
vatpic_intr_accepted(struct vm *vm, int vector)
{
struct vatpic *vatpic;
int pin;
vatpic = vm_atpic(vm);
VATPIC_LOCK(vatpic);
pin = vector & 0x7;
if ((vector & ~0x7) == vatpic->atpic[1].irq_base) {
vatpic_pin_accepted(&vatpic->atpic[1], pin);
vatpic_pin_accepted(&vatpic->atpic[0], 2);
} else {
vatpic_pin_accepted(&vatpic->atpic[0], pin);
}
vatpic_notify_intr(vatpic);
VATPIC_UNLOCK(vatpic);
}
static int
vatpic_read(struct vatpic *vatpic, struct atpic *atpic, bool in, int port,
int bytes, uint32_t *eax)
{
int pin;
VATPIC_LOCK(vatpic);
if (atpic->poll) {
atpic->poll = 0;
pin = vatpic_get_highest_irrpin(atpic);
if (pin >= 0) {
vatpic_pin_accepted(atpic, pin);
*eax = 0x80 | pin;
} else {
*eax = 0;
}
} else {
if (port & ICU_IMR_OFFSET) {
*eax = atpic->mask;
} else {
if (atpic->rd_cmd_reg == OCW3_RIS) {
*eax = atpic->service;
} else {
*eax = atpic->request;
}
}
}
VATPIC_UNLOCK(vatpic);
return (0);
}
static int
vatpic_write(struct vatpic *vatpic, struct atpic *atpic, bool in, int port,
int bytes, uint32_t *eax)
{
int error;
uint8_t val;
error = 0;
val = *eax;
VATPIC_LOCK(vatpic);
if (port & ICU_IMR_OFFSET) {
switch (atpic->icw_num) {
case 2:
error = vatpic_icw2(vatpic, atpic, val);
break;
case 3:
error = vatpic_icw3(vatpic, atpic, val);
break;
case 4:
error = vatpic_icw4(vatpic, atpic, val);
break;
default:
error = vatpic_ocw1(vatpic, atpic, val);
break;
}
} else {
if (val & (1 << 4))
error = vatpic_icw1(vatpic, atpic, val);
if (atpic->ready) {
if (val & (1 << 3))
error = vatpic_ocw3(vatpic, atpic, val);
else
error = vatpic_ocw2(vatpic, atpic, val);
}
}
if (atpic->ready)
vatpic_notify_intr(vatpic);
VATPIC_UNLOCK(vatpic);
return (error);
}
int
vatpic_master_handler(struct vm *vm, bool in, int port, int bytes,
uint32_t *eax)
{
struct vatpic *vatpic;
struct atpic *atpic;
vatpic = vm_atpic(vm);
atpic = &vatpic->atpic[0];
if (bytes != 1)
return (-1);
if (in) {
return (vatpic_read(vatpic, atpic, in, port, bytes, eax));
}
return (vatpic_write(vatpic, atpic, in, port, bytes, eax));
}
int
vatpic_slave_handler(struct vm *vm, bool in, int port, int bytes,
uint32_t *eax)
{
struct vatpic *vatpic;
struct atpic *atpic;
vatpic = vm_atpic(vm);
atpic = &vatpic->atpic[1];
if (bytes != 1)
return (-1);
if (in) {
return (vatpic_read(vatpic, atpic, in, port, bytes, eax));
}
return (vatpic_write(vatpic, atpic, in, port, bytes, eax));
}
int
vatpic_elc_handler(struct vm *vm, bool in, int port, int bytes,
uint32_t *eax)
{
struct vatpic *vatpic;
bool is_master;
vatpic = vm_atpic(vm);
is_master = (port == IO_ELCR1);
if (bytes != 1)
return (-1);
VATPIC_LOCK(vatpic);
if (in) {
if (is_master)
*eax = vatpic->elc[0];
else
*eax = vatpic->elc[1];
} else {
if (is_master)
vatpic->elc[0] = (*eax & 0xf8);
else
vatpic->elc[1] = (*eax & 0xde);
}
VATPIC_UNLOCK(vatpic);
return (0);
}
struct vatpic *
vatpic_init(struct vm *vm)
{
struct vatpic *vatpic;
vatpic = malloc(sizeof(struct vatpic), M_VATPIC, M_WAITOK | M_ZERO);
vatpic->vm = vm;
mtx_init(&vatpic->mtx, "vatpic lock", NULL, MTX_SPIN);
return (vatpic);
}
void
vatpic_cleanup(struct vatpic *vatpic)
{
mtx_destroy(&vatpic->mtx);
free(vatpic, M_VATPIC);
}
#ifdef BHYVE_SNAPSHOT
int
vatpic_snapshot(struct vatpic *vatpic, struct vm_snapshot_meta *meta)
{
int ret;
int i;
struct atpic *atpic;
for (i = 0; i < nitems(vatpic->atpic); i++) {
atpic = &vatpic->atpic[i];
SNAPSHOT_VAR_OR_LEAVE(atpic->ready, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->icw_num, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->rd_cmd_reg, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->aeoi, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->poll, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->rotate, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->sfn, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->irq_base, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->request, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->service, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->mask, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->smm, meta, ret, done);
SNAPSHOT_BUF_OR_LEAVE(atpic->acnt, sizeof(atpic->acnt),
meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->lowprio, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(atpic->intr_raised, meta, ret, done);
}
SNAPSHOT_BUF_OR_LEAVE(vatpic->elc, sizeof(vatpic->elc),
meta, ret, done);
done:
return (ret);
}
#endif