#include <string.h>
#include <sys/types.h>
#include <dev/isa/isareg.h>
#include <dev/vmm/vmm.h>
#include <unistd.h>
#include <pthread.h>
#include "atomicio.h"
#include "i8259.h"
#include "vmd.h"
#include "vmm.h"
struct i8259 {
uint8_t irr;
uint8_t imr;
uint8_t isr;
uint8_t smm;
uint8_t poll;
uint8_t cur_icw;
uint8_t init_mode;
uint8_t vec;
uint8_t irq_conn;
uint8_t next_ocw_read;
uint8_t auto_eoi;
uint8_t rotate_auto_eoi;
uint8_t lowest_pri;
uint8_t asserted;
};
uint8_t elcr[2];
#define PIC_IRR 0
#define PIC_ISR 1
struct i8259 pics[2];
pthread_mutex_t pic_mtx;
static const char *
i8259_pic_name(uint8_t picid)
{
switch (picid) {
case MASTER: return "master";
case SLAVE: return "slave";
default: return "unknown";
}
}
void
i8259_init(void)
{
memset(&pics, 0, sizeof(pics));
pics[MASTER].cur_icw = 1;
pics[SLAVE].cur_icw = 1;
elcr[MASTER] = 0;
elcr[SLAVE] = 0;
if (pthread_mutex_init(&pic_mtx, NULL) != 0)
fatalx("unable to create pic mutex");
}
uint8_t
i8259_is_pending(void)
{
uint8_t master_pending;
uint8_t slave_pending;
mutex_lock(&pic_mtx);
master_pending = pics[MASTER].irr & ~(pics[MASTER].imr | (1 << 2));
slave_pending = pics[SLAVE].irr & ~pics[SLAVE].imr;
mutex_unlock(&pic_mtx);
return (master_pending || slave_pending);
}
uint16_t
i8259_ack(void)
{
uint8_t high_prio_m, high_prio_s;
uint8_t i;
uint16_t ret;
ret = 0xFFFF;
mutex_lock(&pic_mtx);
if (pics[MASTER].asserted == 0 && pics[SLAVE].asserted == 0) {
log_warnx("%s: i8259 ack without assert?", __func__);
goto ret;
}
high_prio_m = pics[MASTER].lowest_pri + 1;
if (high_prio_m > 7)
high_prio_m = 0;
high_prio_s = pics[SLAVE].lowest_pri + 1;
if (high_prio_s > 7)
high_prio_s = 0;
i = high_prio_m;
do {
if ((pics[MASTER].irr & (1 << i)) && i != 2 &&
!(pics[MASTER].imr & (1 << i))) {
pics[MASTER].irr &= ~(1 << i);
pics[MASTER].isr |= (1 << i);
if (pics[MASTER].irr == 0)
pics[MASTER].asserted = 0;
ret = i + pics[MASTER].vec;
goto ret;
}
i++;
if (i > 7)
i = 0;
} while (i != high_prio_m);
i = high_prio_s;
do {
if ((pics[SLAVE].irr & (1 << i)) &&
!(pics[SLAVE].imr & (1 << i))) {
pics[SLAVE].irr &= ~(1 << i);
pics[MASTER].irr &= ~(1 << 2);
pics[SLAVE].isr |= (1 << i);
pics[MASTER].isr |= (1 << 2);
if (pics[SLAVE].irr == 0) {
pics[SLAVE].asserted = 0;
if (pics[MASTER].irr == 0)
pics[MASTER].asserted = 0;
}
ret = i + pics[SLAVE].vec;
goto ret;
}
i++;
if (i > 7)
i = 0;
} while (i != high_prio_s);
log_warnx("%s: ack without pending irq?", __func__);
ret:
mutex_unlock(&pic_mtx);
return (ret);
}
void
i8259_assert_irq(uint8_t irq)
{
mutex_lock(&pic_mtx);
if (irq <= 7) {
SET(pics[MASTER].irr, 1 << irq);
pics[MASTER].asserted = 1;
} else {
irq -= 8;
SET(pics[SLAVE].irr, 1 << irq);
pics[SLAVE].asserted = 1;
SET(pics[MASTER].irr, 1 << 2);
pics[MASTER].asserted = 1;
}
mutex_unlock(&pic_mtx);
}
void
i8259_deassert_irq(uint8_t irq)
{
mutex_lock(&pic_mtx);
if (irq <= 7) {
if (elcr[MASTER] & (1 << irq))
CLR(pics[MASTER].irr, 1 << irq);
} else {
irq -= 8;
if (elcr[SLAVE] & (1 << irq)) {
CLR(pics[SLAVE].irr, 1 << irq);
if (pics[SLAVE].irr == 0)
CLR(pics[MASTER].irr, 1 << 2);
}
}
mutex_unlock(&pic_mtx);
}
static void
i8259_write_datareg(uint8_t n, uint8_t data)
{
struct i8259 *pic = &pics[n];
if (pic->init_mode == 1) {
if (pic->cur_icw == 2) {
log_debug("%s: %s pic, reset IRQ vector to 0x%x",
__func__, i8259_pic_name(n), data);
pic->vec = data;
} else if (pic->cur_icw == 3) {
if (n == SLAVE && (data & 0xf8)) {
log_warnx("%s: %s pic invalid icw2 0x%x",
__func__, i8259_pic_name(n), data);
return;
}
pic->irq_conn = data;
} else if (pic->cur_icw == 4) {
if (!(data & ICW4_UP)) {
log_warnx("%s: %s pic init error: x86 bit "
"clear", __func__, i8259_pic_name(n));
return;
}
if (data & ICW4_AEOI) {
log_warnx("%s: %s pic: aeoi mode set",
__func__, i8259_pic_name(n));
pic->auto_eoi = 1;
return;
}
if (data & ICW4_MS) {
log_warnx("%s: %s pic init error: M/S mode",
__func__, i8259_pic_name(n));
return;
}
if (data & ICW4_BUF) {
log_warnx("%s: %s pic init error: buf mode",
__func__, i8259_pic_name(n));
return;
}
if (data & 0xe0) {
log_warnx("%s: %s pic init error: invalid icw4 "
" 0x%x", __func__, i8259_pic_name(n), data);
return;
}
}
pic->cur_icw++;
if (pic->cur_icw == 5) {
pic->cur_icw = 1;
pic->init_mode = 0;
}
} else
pic->imr = data;
}
static void
i8259_specific_eoi(uint8_t n, uint8_t data)
{
if (!(pics[n].isr & (1 << (data & 0x7)))) {
log_warnx("%s: %s pic specific eoi irq %d while not in"
" service", __func__, i8259_pic_name(n), (data & 0x7));
}
pics[n].isr &= ~(1 << (data & 0x7));
}
static void
i8259_nonspecific_eoi(uint8_t n, uint8_t data)
{
int i = 0;
while (i < 8) {
if ((pics[n].isr & (1 << (i & 0x7)))) {
i8259_specific_eoi(n, i);
return;
}
i++;
}
}
static void
i8259_rotate_priority(uint8_t n)
{
pics[n].lowest_pri++;
if (pics[n].lowest_pri > 7)
pics[n].lowest_pri = 0;
}
static void
i8259_write_cmdreg(uint8_t n, uint8_t data)
{
struct i8259 *pic = &pics[n];
if (data & ICW1_INIT) {
if (!(data & ICW1_ICW4)) {
log_warnx("%s: %s pic init error: no ICW4 request",
__func__, i8259_pic_name(n));
return;
}
if (data & (ICW1_IVA1 | ICW1_IVA2 | ICW1_IVA3)) {
log_warnx("%s: %s pic init error: IVA specified",
__func__, i8259_pic_name(n));
return;
}
if (data & ICW1_SNGL) {
log_warnx("%s: %s pic init error: single pic mode",
__func__, i8259_pic_name(n));
return;
}
if (data & ICW1_ADI) {
log_warnx("%s: %s pic init error: address interval",
__func__, i8259_pic_name(n));
return;
}
if (data & ICW1_LTIM) {
log_warnx("%s: %s pic init error: level trigger mode",
__func__, i8259_pic_name(n));
return;
}
pic->init_mode = 1;
pic->cur_icw = 2;
pic->imr = 0;
pic->isr = 0;
pic->irr = 0;
pic->asserted = 0;
pic->lowest_pri = 7;
pic->rotate_auto_eoi = 0;
return;
} else if (data & OCW_SELECT) {
if (data & OCW3_ACTION) {
if (data & OCW3_RR) {
if (data & OCW3_RIS)
pic->next_ocw_read = PIC_ISR;
else
pic->next_ocw_read = PIC_IRR;
}
}
if (data & OCW3_SMACTION) {
if (data & OCW3_SMM) {
pic->smm = 1;
} else
pic->smm = 0;
}
if (data & OCW3_POLL) {
pic->poll = 1;
}
return;
} else {
if (data & OCW2_EOI) {
switch (data) {
case OCW2_EOI:
i8259_nonspecific_eoi(n, data);
break;
case OCW2_SEOI ... OCW2_SEOI + 7:
i8259_specific_eoi(n, data);
break;
case OCW2_ROTATE_NSEOI:
i8259_nonspecific_eoi(n, data);
i8259_rotate_priority(n);
break;
case OCW2_ROTATE_SEOI ... OCW2_ROTATE_SEOI + 7:
i8259_specific_eoi(n, data);
i8259_rotate_priority(n);
break;
}
return;
}
if (data == OCW2_NOP)
return;
if ((data & OCW2_SET_LOWPRIO) == OCW2_SET_LOWPRIO) {
pic->lowest_pri = data & 0x7;
return;
}
if (data == OCW2_ROTATE_AEOI_CLEAR) {
pic->rotate_auto_eoi = 0;
return;
}
if (data == OCW2_ROTATE_AEOI_SET) {
pic->rotate_auto_eoi = 1;
return;
}
return;
}
}
static uint8_t
i8259_read_datareg(uint8_t n)
{
struct i8259 *pic = &pics[n];
return (pic->imr);
}
static uint8_t
i8259_read_cmdreg(uint8_t n)
{
struct i8259 *pic = &pics[n];
if (pic->next_ocw_read == PIC_IRR)
return (pic->irr);
else if (pic->next_ocw_read == PIC_ISR)
return (pic->isr);
fatal("%s: invalid PIC config during cmdreg read", __func__);
}
static void
i8259_io_write(struct vm_exit *vei)
{
uint16_t port = vei->vei.vei_port;
uint32_t data = 0;
uint8_t n = 0;
get_input_data(vei, &data);
switch (port) {
case IO_ICU1:
case IO_ICU1 + 1:
n = MASTER;
break;
case IO_ICU2:
case IO_ICU2 + 1:
n = SLAVE;
break;
default:
fatal("%s: invalid port 0x%x", __func__, port);
}
mutex_lock(&pic_mtx);
if (port == IO_ICU1 + 1 || port == IO_ICU2 + 1)
i8259_write_datareg(n, data);
else
i8259_write_cmdreg(n, data);
mutex_unlock(&pic_mtx);
}
static uint8_t
i8259_io_read(struct vm_exit *vei)
{
uint16_t port = vei->vei.vei_port;
uint8_t n = 0;
uint8_t rv;
switch (port) {
case IO_ICU1:
case IO_ICU1 + 1:
n = MASTER;
break;
case IO_ICU2:
case IO_ICU2 + 1:
n = SLAVE;
break;
default:
fatal("%s: invalid port 0x%x", __func__, port);
}
mutex_lock(&pic_mtx);
if (port == IO_ICU1 + 1 || port == IO_ICU2 + 1)
rv = i8259_read_datareg(n);
else
rv = i8259_read_cmdreg(n);
mutex_unlock(&pic_mtx);
return (rv);
}
uint8_t
vcpu_exit_i8259(struct vm_run_params *vrp)
{
struct vm_exit *vei = vrp->vrp_exit;
if (vei->vei.vei_dir == VEI_DIR_OUT) {
i8259_io_write(vei);
} else {
set_return_data(vei, i8259_io_read(vei));
}
return (0xFF);
}
void
pic_set_elcr(uint8_t irq, uint8_t val)
{
if (irq > 15 || val > 1)
return;
log_debug("%s: setting %s triggered mode for irq %d", __func__,
val ? "level" : "edge", irq);
if (irq > 7) {
if (val)
elcr[SLAVE] |= (1 << (irq - 8));
else
elcr[SLAVE] &= ~(1 << (irq - 8));
} else {
if (val)
elcr[MASTER] |= (1 << irq);
else
elcr[MASTER] &= ~(1 << irq);
}
}
uint8_t
vcpu_exit_elcr(struct vm_run_params *vrp)
{
struct vm_exit *vei = vrp->vrp_exit;
uint8_t elcr_reg = vei->vei.vei_port - ELCR0;
if (elcr_reg > 1) {
log_debug("%s: invalid ELCR index %d", __func__, elcr_reg);
return (0xFF);
}
if (vei->vei.vei_dir == VEI_DIR_OUT) {
log_debug("%s: ELCR[%d] set to 0x%x", __func__, elcr_reg,
(uint8_t)vei->vei.vei_data);
elcr[elcr_reg] = (uint8_t)vei->vei.vei_data;
} else {
set_return_data(vei, elcr[elcr_reg]);
}
return (0xFF);
}