#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <machine/vmm.h>
#include "vatpic.h"
#include "vatpit.h"
#include "vrtc.h"
#include "vmm_ioport.h"
static uint_t ioport_entry_limit = 64;
static void
vm_inout_def(ioport_entry_t *entries, uint_t i, uint16_t port,
ioport_handler_t func, void *arg, uint16_t flags)
{
ioport_entry_t *ent = &entries[i];
if (i != 0) {
const ioport_entry_t *prev = &entries[i - 1];
VERIFY(prev->iope_port < port);
}
ent->iope_func = func;
ent->iope_arg = arg;
ent->iope_port = port;
ent->iope_flags = flags;
}
void
vm_inout_init(struct vm *vm, struct ioport_config *cfg)
{
struct vatpit *pit = vm_atpit(vm);
struct vatpic *pic = vm_atpic(vm);
struct vrtc *rtc = vm_rtc(vm);
const uint_t ndefault = 13;
const uint16_t flag = IOPF_FIXED;
ioport_entry_t *ents;
uint_t i = 0;
VERIFY0(cfg->iop_entries);
VERIFY0(cfg->iop_count);
ents = kmem_zalloc(ndefault * sizeof (ioport_entry_t), KM_SLEEP);
vm_inout_def(ents, i++, IO_ICU1, vatpic_master_handler, pic, flag);
vm_inout_def(ents, i++, IO_ICU1 + ICU_IMR_OFFSET, vatpic_master_handler,
pic, flag);
vm_inout_def(ents, i++, TIMER_CNTR0, vatpit_handler, pit, flag);
vm_inout_def(ents, i++, TIMER_CNTR1, vatpit_handler, pit, flag);
vm_inout_def(ents, i++, TIMER_CNTR2, vatpit_handler, pit, flag);
vm_inout_def(ents, i++, TIMER_MODE, vatpit_handler, pit, flag);
vm_inout_def(ents, i++, NMISC_PORT, vatpit_nmisc_handler, pit, flag);
vm_inout_def(ents, i++, IO_RTC, vrtc_addr_handler, rtc, flag);
vm_inout_def(ents, i++, IO_RTC + 1, vrtc_data_handler, rtc, flag);
vm_inout_def(ents, i++, IO_ICU2, vatpic_slave_handler, pic, flag);
vm_inout_def(ents, i++, IO_ICU2 + ICU_IMR_OFFSET, vatpic_slave_handler,
pic, flag);
vm_inout_def(ents, i++, IO_ELCR1, vatpic_elc_handler, pic, flag);
vm_inout_def(ents, i++, IO_ELCR2, vatpic_elc_handler, pic, flag);
VERIFY3U(i, ==, ndefault);
cfg->iop_entries = ents;
cfg->iop_count = ndefault;
}
void
vm_inout_cleanup(struct vm *vm, struct ioport_config *cfg)
{
VERIFY(cfg->iop_entries);
VERIFY(cfg->iop_count);
kmem_free(cfg->iop_entries,
sizeof (ioport_entry_t) * cfg->iop_count);
cfg->iop_entries = NULL;
cfg->iop_count = 0;
}
static void
vm_inout_remove_at(uint_t idx, uint_t old_count, ioport_entry_t *old_ents,
ioport_entry_t *new_ents)
{
uint_t new_count = old_count - 1;
VERIFY(old_count != 0);
VERIFY(idx < old_count);
if (idx > 0) {
bcopy(old_ents, new_ents, sizeof (ioport_entry_t) * idx);
}
if (idx < new_count) {
bcopy(&old_ents[idx + 1], &new_ents[idx],
sizeof (ioport_entry_t) * (new_count - idx));
}
}
static void
vm_inout_insert_space_at(uint_t idx, uint_t old_count, ioport_entry_t *old_ents,
ioport_entry_t *new_ents)
{
uint_t new_count = old_count + 1;
VERIFY(idx < new_count);
if (idx > 0) {
bcopy(old_ents, new_ents, sizeof (ioport_entry_t) * idx);
}
if (idx < new_count) {
bcopy(&old_ents[idx], &new_ents[idx + 1],
sizeof (ioport_entry_t) * (old_count - idx));
}
}
int
vm_inout_attach(struct ioport_config *cfg, uint16_t port, uint16_t flags,
ioport_handler_t func, void *arg)
{
uint_t i, old_count, insert_idx;
ioport_entry_t *old_ents;
if (cfg->iop_count >= ioport_entry_limit) {
return (ENOSPC);
}
old_count = cfg->iop_count;
old_ents = cfg->iop_entries;
for (insert_idx = i = 0; i < old_count; i++) {
const ioport_entry_t *compare = &old_ents[i];
if (compare->iope_port == port) {
return (EEXIST);
} else if (compare->iope_port < port) {
insert_idx = i + 1;
}
}
ioport_entry_t *new_ents;
uint_t new_count = old_count + 1;
new_ents = kmem_alloc(new_count * sizeof (ioport_entry_t), KM_SLEEP);
vm_inout_insert_space_at(insert_idx, old_count, old_ents, new_ents);
new_ents[insert_idx].iope_func = func;
new_ents[insert_idx].iope_arg = arg;
new_ents[insert_idx].iope_port = port;
new_ents[insert_idx].iope_flags = flags;
new_ents[insert_idx].iope_pad = 0;
cfg->iop_entries = new_ents;
cfg->iop_count = new_count;
kmem_free(old_ents, old_count * sizeof (ioport_entry_t));
return (0);
}
int
vm_inout_detach(struct ioport_config *cfg, uint16_t port, bool drv_hook,
ioport_handler_t *old_func, void **old_arg)
{
uint_t i, old_count, remove_idx;
ioport_entry_t *old_ents;
old_count = cfg->iop_count;
old_ents = cfg->iop_entries;
VERIFY(old_count > 1);
for (i = 0; i < old_count; i++) {
const ioport_entry_t *compare = &old_ents[i];
if (compare->iope_port != port) {
continue;
}
if ((compare->iope_flags & IOPF_FIXED) != 0) {
return (EPERM);
}
if (drv_hook && (compare->iope_flags & IOPF_DRV_HOOK) == 0) {
return (EPERM);
} else if (!drv_hook &&
(compare->iope_flags & IOPF_DRV_HOOK) != 0) {
return (EPERM);
}
break;
}
if (i == old_count) {
return (ENOENT);
}
remove_idx = i;
if (old_func != NULL) {
*old_func = cfg->iop_entries[remove_idx].iope_func;
}
if (old_arg != NULL) {
*old_arg = cfg->iop_entries[remove_idx].iope_arg;
}
ioport_entry_t *new_ents;
uint_t new_count = old_count - 1;
new_ents = kmem_alloc(new_count * sizeof (ioport_entry_t), KM_SLEEP);
vm_inout_remove_at(remove_idx, old_count, old_ents, new_ents);
cfg->iop_entries = new_ents;
cfg->iop_count = new_count;
kmem_free(old_ents, old_count * sizeof (ioport_entry_t));
return (0);
}
static ioport_entry_t *
vm_inout_find(const struct ioport_config *cfg, uint16_t port)
{
const uint_t count = cfg->iop_count;
ioport_entry_t *entries = cfg->iop_entries;
for (uint_t i = 0; i < count; i++) {
if (entries[i].iope_port == port) {
return (&entries[i]);
}
}
return (NULL);
}
int
vm_inout_access(struct ioport_config *cfg, bool in, uint16_t port,
uint8_t bytes, uint32_t *val)
{
const ioport_entry_t *ent;
int err;
ent = vm_inout_find(cfg, port);
if (ent == NULL) {
err = ESRCH;
} else {
err = ent->iope_func(ent->iope_arg, in, port, bytes, val);
}
return (err);
}