#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/eventhandler.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#include <machine/cpu.h>
#include <machine/md_var.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/pci.h>
#include "vmm_util.h"
#include "iommu.h"
static kmutex_t iommu_lock;
static uint_t iommu_refcnt;
ddi_modhandle_t iommu_modhdl;
static const struct iommu_ops *ops;
static void *host_domain;
static int
iommu_find_device(dev_info_t *dip, void *arg)
{
boolean_t add = (boolean_t)(uintptr_t)arg;
if (pcie_is_pci_device(dip)) {
if (add)
iommu_add_device(host_domain, pci_get_rid(dip));
else
iommu_remove_device(host_domain, pci_get_rid(dip));
}
return (DDI_WALK_CONTINUE);
}
static vm_paddr_t
vmm_mem_maxaddr(void)
{
return (ptoa(physmax + 1));
}
static int
iommu_init(void)
{
const char *mod_name;
int error = 0;
ASSERT(MUTEX_HELD(&iommu_lock));
if (vmm_is_intel()) {
mod_name = "misc/vmm_vtd";
} else if (vmm_is_svm()) {
mod_name = "misc/vmm_amdvi";
} else {
return (ENXIO);
}
iommu_modhdl = ddi_modopen(mod_name, KRTLD_MODE_FIRST, &error);
if (iommu_modhdl == NULL) {
return (error);
}
ops = ddi_modsym(iommu_modhdl, IOMMU_OPS_SYM_NAME, &error);
if (ops == NULL) {
goto bail;
}
error = ops->init();
if (error != 0) {
goto bail;
}
const vm_paddr_t maxaddr = vmm_mem_maxaddr();
host_domain = ops->create_domain(maxaddr);
if (host_domain == NULL) {
goto bail;
}
iommu_create_mapping(host_domain, 0, 0, maxaddr);
ddi_walk_devs(ddi_root_node(), iommu_find_device, (void *)B_TRUE);
ops->enable();
return (0);
bail:
if (ops != NULL) {
ops->cleanup();
ops = NULL;
}
if (iommu_modhdl != NULL) {
(void) ddi_modclose(iommu_modhdl);
iommu_modhdl = NULL;
}
return (error);
}
static void
iommu_cleanup(void)
{
ASSERT(MUTEX_HELD(&iommu_lock));
ASSERT3P(ops, !=, NULL);
ASSERT0(iommu_refcnt);
ops->disable();
ddi_walk_devs(ddi_root_node(), iommu_find_device, (void *)B_FALSE);
ops->destroy_domain(host_domain);
host_domain = NULL;
ops->cleanup();
ops = NULL;
(void) ddi_modclose(iommu_modhdl);
iommu_modhdl = NULL;
}
static bool
iommu_ref(void)
{
mutex_enter(&iommu_lock);
if (ops == NULL) {
int err = iommu_init();
if (err != 0) {
VERIFY3P(ops, ==, NULL);
mutex_exit(&iommu_lock);
return (false);
}
VERIFY3P(ops, !=, NULL);
}
iommu_refcnt++;
VERIFY3U(iommu_refcnt, <, UINT_MAX);
mutex_exit(&iommu_lock);
return (true);
}
static void
iommu_unref(void)
{
mutex_enter(&iommu_lock);
VERIFY3U(iommu_refcnt, >, 0);
iommu_refcnt--;
if (iommu_refcnt == 0) {
iommu_cleanup();
VERIFY3P(ops, ==, NULL);
}
mutex_exit(&iommu_lock);
}
void *
iommu_create_domain(vm_paddr_t maxaddr)
{
if (iommu_ref()) {
return (ops->create_domain(maxaddr));
} else {
return (NULL);
}
}
void
iommu_destroy_domain(void *domain)
{
ASSERT3P(domain, !=, NULL);
ops->destroy_domain(domain);
iommu_unref();
}
void
iommu_create_mapping(void *domain, vm_paddr_t gpa, vm_paddr_t hpa, size_t len)
{
uint64_t remaining = len;
ASSERT3P(domain, !=, NULL);
while (remaining > 0) {
uint64_t mapped;
mapped = ops->create_mapping(domain, gpa, hpa, remaining);
gpa += mapped;
hpa += mapped;
remaining -= mapped;
}
}
void
iommu_remove_mapping(void *domain, vm_paddr_t gpa, size_t len)
{
uint64_t remaining = len;
ASSERT3P(domain, !=, NULL);
while (remaining > 0) {
uint64_t unmapped;
unmapped = ops->remove_mapping(domain, gpa, remaining);
gpa += unmapped;
remaining -= unmapped;
}
}
void *
iommu_host_domain(void)
{
return (host_domain);
}
void
iommu_add_device(void *domain, uint16_t rid)
{
ASSERT3P(domain, !=, NULL);
ops->add_device(domain, rid);
}
void
iommu_remove_device(void *domain, uint16_t rid)
{
ASSERT3P(domain, !=, NULL);
ops->remove_device(domain, rid);
}
void
iommu_invalidate_tlb(void *domain)
{
ASSERT3P(domain, !=, NULL);
ops->invalidate_tlb(domain);
}