Changeset View
Changeset View
Standalone View
Standalone View
sys/amd64/vmm/intel/dmar_iommu.c
- This file was added.
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
* | |||||
* Copyright (c) 2019 The FreeBSD Foundation | |||||
* All rights reserved. | |||||
* | |||||
* This software was developed by Konstantin Belousov <kib@FreeBSD.org> | |||||
* under sponsorship from the FreeBSD Foundation. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* 2. Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
* SUCH DAMAGE. | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/param.h> | |||||
#include <sys/systm.h> | |||||
#include <sys/bus.h> | |||||
#include <sys/kernel.h> | |||||
#include <sys/limits.h> | |||||
#include <sys/lock.h> | |||||
#include <sys/malloc.h> | |||||
#include <sys/memdesc.h> | |||||
#include <sys/rman.h> | |||||
#include <sys/sx.h> | |||||
#include <sys/sysctl.h> | |||||
#include <sys/taskqueue.h> | |||||
#include <sys/tree.h> | |||||
#include <sys/vmem.h> | |||||
#include <machine/bus.h> | |||||
#include <vm/vm.h> | |||||
#include <vm/vm_extern.h> | |||||
#include <vm/vm_object.h> | |||||
#include <vm/vm_page.h> | |||||
#include <dev/pci/pcireg.h> | |||||
#include <x86/include/busdma_impl.h> | |||||
#include <x86/iommu/intel_reg.h> | |||||
#include <dev/iommu/busdma_iommu.h> | |||||
#include <x86/iommu/intel_dmar.h> | |||||
#include "io/iommu.h" | |||||
static MALLOC_DEFINE(M_DMAR_VMM, "dmar_vmm", "DMAR VMM memory"); | |||||
struct dmar_vmm_domain { | |||||
bool host_domain; | |||||
struct sx lock; | |||||
LIST_HEAD(, dmar_domain) dmar_domains; | |||||
}; | |||||
static int | |||||
dmar_init(void) | |||||
{ | |||||
return (dmar_is_running()); | |||||
} | |||||
static void | |||||
dmar_cleanup(void) | |||||
{ | |||||
/* XXXKIB */ | |||||
} | |||||
static void | |||||
dmar_enable(void) | |||||
{ | |||||
/* XXXKIB */ | |||||
} | |||||
static void | |||||
dmar_disable(void) | |||||
{ | |||||
/* XXXKIB */ | |||||
} | |||||
static void * | |||||
dmar_create_domain(vm_paddr_t maxaddr, bool host_domain) | |||||
{ | |||||
struct dmar_vmm_domain *vmm_dom; | |||||
vmm_dom = malloc(sizeof(struct dmar_vmm_domain), M_DMAR_VMM, | |||||
M_WAITOK | M_ZERO); | |||||
LIST_INIT(&vmm_dom->dmar_domains); | |||||
sx_init(&vmm_dom->lock, "vmmdom"); | |||||
vmm_dom->host_domain = host_domain; | |||||
return (vmm_dom); | |||||
} | |||||
static void | |||||
dmar_destroy_domain(void *domain) | |||||
{ | |||||
struct dmar_vmm_domain *vmm_dom; | |||||
vmm_dom = domain; | |||||
#if 0 | |||||
LIST_FOREACH_SAFE() { | |||||
} | |||||
#endif | |||||
sx_destroy(&vmm_dom->lock); | |||||
free(vmm_dom, M_DMAR_VMM); | |||||
} | |||||
static int | |||||
dmar_create_mapping_onedmar(struct dmar_domain *dom, vm_paddr_t gpa, | |||||
vm_paddr_t hpa, uint64_t len) | |||||
{ | |||||
struct iommu_map_entry *e; | |||||
vm_page_t fm, m, *ma; | |||||
vm_paddr_t pa; | |||||
iommu_gaddr_t glen, gtotal_len; | |||||
u_long cnt_after; | |||||
int error; | |||||
fm = NULL; | |||||
for (pa = hpa, gtotal_len = 0; gtotal_len < len; pa += glen, | |||||
gpa += glen, gtotal_len += glen) { | |||||
e = iommu_gas_alloc_entry(&dom->iodom, IOMMU_PGF_WAITOK); | |||||
e->start = gpa; | |||||
m = vm_page_phys_to_vm_page(pa, &cnt_after); | |||||
if (m == NULL) { | |||||
if (fm == NULL) | |||||
fm = vm_page_getfake(pa, VM_MEMATTR_DEFAULT); | |||||
else | |||||
vm_page_updatefake(fm, pa, VM_MEMATTR_DEFAULT); | |||||
ma = &fm; | |||||
glen = PAGE_SIZE; | |||||
} else { | |||||
ma = &m; | |||||
glen = MIN(len - gtotal_len, ptoa(cnt_after + 1)); | |||||
} | |||||
e->end = gpa + glen; | |||||
error = iommu_gas_map_region(&dom->iodom, e, | |||||
IOMMU_MAP_ENTRY_READ | IOMMU_MAP_ENTRY_WRITE, | |||||
IOMMU_MF_CANWAIT | IOMMU_MF_CANSPLIT | IOMMU_MF_VMM, ma); | |||||
if (error != 0) | |||||
break; | |||||
glen = e->end - e->start; | |||||
} | |||||
if (fm != NULL) | |||||
vm_page_putfake(fm); | |||||
return (error); | |||||
} | |||||
static int | |||||
dmar_create_mapping(void *dom1, vm_paddr_t gpa, vm_paddr_t hpa, uint64_t len, | |||||
uint64_t *res_len) | |||||
{ | |||||
struct dmar_vmm_domain *vmm_dom; | |||||
struct dmar_domain *dom; | |||||
int error; | |||||
vmm_dom = dom1; | |||||
error = 0; | |||||
//printf("dmar_create_mapping (%d): gpa %#lx hpa %#lx len %#lx\n", vmm_dom->host_domain, gpa, hpa, len); | |||||
if (vmm_dom->host_domain) { | |||||
sx_xlock(&vmm_dom->lock); | |||||
LIST_FOREACH(dom, &vmm_dom->dmar_domains, vmm_dom_link) { | |||||
error = dmar_create_mapping_onedmar(dom, gpa, hpa, len); | |||||
if (error != 0) | |||||
break; | |||||
} | |||||
sx_xunlock(&vmm_dom->lock); | |||||
} | |||||
*res_len = len; | |||||
return (error); | |||||
} | |||||
static int | |||||
dmar_create_mapping_bulk_onedmar(struct dmar_domain *dom, vm_paddr_t gpa, | |||||
struct vm_page **ma, uint64_t len) | |||||
{ | |||||
struct iommu_map_entry *e; | |||||
iommu_gaddr_t gtotal_len, glen; | |||||
int error; | |||||
for (gtotal_len = 0; gtotal_len < len; gpa += glen, | |||||
gtotal_len += glen) { | |||||
e = iommu_gas_alloc_entry(&dom->iodom, IOMMU_PGF_WAITOK); | |||||
glen = len - gtotal_len; | |||||
e->start = gpa; | |||||
e->end = gpa + glen; | |||||
error = iommu_gas_map_region(&dom->iodom, e, | |||||
IOMMU_MAP_ENTRY_READ | IOMMU_MAP_ENTRY_WRITE, | |||||
IOMMU_MF_CANWAIT | IOMMU_MF_CANSPLIT | IOMMU_MF_VMM, | |||||
ma + atop(gtotal_len)); | |||||
if (error != 0) | |||||
break; | |||||
glen = e->end - e->start; | |||||
} | |||||
return (error); | |||||
} | |||||
static int | |||||
dmar_create_mapping_bulk(void *dom1, vm_paddr_t gpa, struct vm_page **ma, | |||||
uint64_t len) | |||||
{ | |||||
struct dmar_vmm_domain *vmm_dom; | |||||
struct dmar_domain *dom; | |||||
int error; | |||||
vmm_dom = dom1; | |||||
error = 0; | |||||
if (!vmm_dom->host_domain) { | |||||
sx_xlock(&vmm_dom->lock); | |||||
LIST_FOREACH(dom, &vmm_dom->dmar_domains, vmm_dom_link) { | |||||
error = dmar_create_mapping_bulk_onedmar(dom, gpa, | |||||
ma, len); | |||||
if (error != 0) | |||||
break; | |||||
} | |||||
sx_xunlock(&vmm_dom->lock); | |||||
} | |||||
return (error); | |||||
} | |||||
static int | |||||
dmar_remove_mapping(void *dom1, vm_paddr_t gpa, uint64_t len, uint64_t *res_len) | |||||
{ | |||||
struct dmar_vmm_domain *vmm_dom; | |||||
struct dmar_domain *dom; | |||||
vmm_dom = dom1; | |||||
if (!vmm_dom->host_domain) { | |||||
sx_xlock(&vmm_dom->lock); | |||||
LIST_FOREACH(dom, &vmm_dom->dmar_domains, vmm_dom_link) | |||||
iommu_gas_remove(&dom->iodom, gpa, gpa + len); | |||||
sx_xunlock(&vmm_dom->lock); | |||||
} | |||||
*res_len = len; | |||||
return (0); | |||||
} | |||||
static int | |||||
dmar_add_device(void *dom1, device_t dev, uint16_t rid) | |||||
{ | |||||
struct dmar_vmm_domain *vmm_dom; | |||||
struct dmar_unit *dmar; | |||||
struct dmar_domain *domain, *rdomain; | |||||
int error; | |||||
vmm_dom = dom1; | |||||
if (vmm_dom->host_domain) | |||||
return (0); | |||||
error = 0; | |||||
sx_xlock(&vmm_dom->lock); | |||||
dmar = dmar_find(dev, true); | |||||
if (dmar == NULL) { | |||||
error = ENOTTY; | |||||
goto done; | |||||
} | |||||
LIST_FOREACH(domain, &vmm_dom->dmar_domains, vmm_dom_link) { | |||||
if (domain->dmar == dmar) | |||||
break; | |||||
} | |||||
rdomain = dmar_vmm_domain_add_dev(&dmar->iommu, domain, dev, rid); | |||||
if (rdomain == NULL) { | |||||
error = EBUSY; | |||||
goto done; | |||||
} | |||||
if (domain == NULL || domain != rdomain) { | |||||
LIST_INSERT_HEAD(&vmm_dom->dmar_domains, rdomain, | |||||
vmm_dom_link); | |||||
} | |||||
done: | |||||
sx_xunlock(&vmm_dom->lock); | |||||
return (error); | |||||
} | |||||
static int | |||||
dmar_remove_device(void *dom1, device_t dev, uint16_t rid) | |||||
{ | |||||
struct dmar_vmm_domain *vmm_dom; | |||||
vmm_dom = dom1; | |||||
if (vmm_dom->host_domain) | |||||
return (0); | |||||
/* XXXKIB */ | |||||
return (0); | |||||
} | |||||
static int | |||||
dmar_invalidate_tlb(void *dom) | |||||
{ | |||||
/* XXXKIB: do nothing, rely on map/unmap ? */ | |||||
return (0); | |||||
} | |||||
static bool | |||||
dmar_get_swcaps(enum iommu_swcaps cap) | |||||
{ | |||||
switch (cap) { | |||||
case IOMMU_CAP_BULK: | |||||
return (true); | |||||
default: | |||||
return (false); | |||||
} | |||||
} | |||||
const struct iommu_ops iommu_ops_dmar = { | |||||
.init = dmar_init, | |||||
.cleanup = dmar_cleanup, | |||||
.enable = dmar_enable, | |||||
.disable = dmar_disable, | |||||
.create_domain = dmar_create_domain, | |||||
.destroy_domain = dmar_destroy_domain, | |||||
.create_mapping = dmar_create_mapping, | |||||
.create_mapping_bulk = dmar_create_mapping_bulk, | |||||
.remove_mapping = dmar_remove_mapping, | |||||
.add_device = dmar_add_device, | |||||
.remove_device = dmar_remove_device, | |||||
.invalidate_tlb = dmar_invalidate_tlb, | |||||
.get_swcaps = dmar_get_swcaps, | |||||
}; |