Browse Source
This is a library to manage the host vfio interface, which could be used to implement userspace device driver code in QEMU such as NVMe or net controllers. Signed-off-by: Fam Zheng <famz@redhat.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Message-Id: <20180116060901.17413-3-famz@redhat.com> Signed-off-by: Fam Zheng <famz@redhat.com>pull/66/head
4 changed files with 772 additions and 0 deletions
@ -0,0 +1,33 @@ |
|||
/*
|
|||
* QEMU VFIO helpers |
|||
* |
|||
* Copyright 2016 - 2018 Red Hat, Inc. |
|||
* |
|||
* Authors: |
|||
* Fam Zheng <famz@redhat.com> |
|||
* |
|||
* This work is licensed under the terms of the GNU GPL, version 2 or later. |
|||
* See the COPYING file in the top-level directory. |
|||
*/ |
|||
|
|||
#ifndef QEMU_VFIO_HELPERS_H |
|||
#define QEMU_VFIO_HELPERS_H |
|||
#include "qemu/typedefs.h" |
|||
|
|||
typedef struct QEMUVFIOState QEMUVFIOState; |
|||
|
|||
QEMUVFIOState *qemu_vfio_open_pci(const char *device, Error **errp); |
|||
void qemu_vfio_close(QEMUVFIOState *s); |
|||
int qemu_vfio_dma_map(QEMUVFIOState *s, void *host, size_t size, |
|||
bool temporary, uint64_t *iova_list); |
|||
int qemu_vfio_dma_reset_temporary(QEMUVFIOState *s); |
|||
void qemu_vfio_dma_unmap(QEMUVFIOState *s, void *host); |
|||
void *qemu_vfio_pci_map_bar(QEMUVFIOState *s, int index, |
|||
uint64_t offset, uint64_t size, |
|||
Error **errp); |
|||
void qemu_vfio_pci_unmap_bar(QEMUVFIOState *s, int index, void *bar, |
|||
uint64_t offset, uint64_t size); |
|||
int qemu_vfio_pci_init_irq(QEMUVFIOState *s, EventNotifier *e, |
|||
int irq_type, Error **errp); |
|||
|
|||
#endif |
|||
@ -0,0 +1,727 @@ |
|||
/*
|
|||
* VFIO utility |
|||
* |
|||
* Copyright 2016 - 2018 Red Hat, Inc. |
|||
* |
|||
* Authors: |
|||
* Fam Zheng <famz@redhat.com> |
|||
* |
|||
* This work is licensed under the terms of the GNU GPL, version 2 or later. |
|||
* See the COPYING file in the top-level directory. |
|||
*/ |
|||
|
|||
#include "qemu/osdep.h" |
|||
#include <sys/ioctl.h> |
|||
#include <linux/vfio.h> |
|||
#include "qapi/error.h" |
|||
#include "exec/ramlist.h" |
|||
#include "exec/cpu-common.h" |
|||
#include "trace.h" |
|||
#include "qemu/queue.h" |
|||
#include "qemu/error-report.h" |
|||
#include "standard-headers/linux/pci_regs.h" |
|||
#include "qemu/event_notifier.h" |
|||
#include "qemu/vfio-helpers.h" |
|||
#include "trace.h" |
|||
|
|||
#define QEMU_VFIO_DEBUG 0 |
|||
|
|||
#define QEMU_VFIO_IOVA_MIN 0x10000ULL |
|||
/* XXX: Once VFIO exposes the iova bit width in the IOMMU capability interface,
|
|||
* we can use a runtime limit; alternatively it's also possible to do platform |
|||
* specific detection by reading sysfs entries. Until then, 39 is a safe bet. |
|||
**/ |
|||
#define QEMU_VFIO_IOVA_MAX (1ULL << 39) |
|||
|
|||
typedef struct { |
|||
/* Page aligned addr. */ |
|||
void *host; |
|||
size_t size; |
|||
uint64_t iova; |
|||
} IOVAMapping; |
|||
|
|||
struct QEMUVFIOState { |
|||
QemuMutex lock; |
|||
|
|||
/* These fields are protected by BQL */ |
|||
int container; |
|||
int group; |
|||
int device; |
|||
RAMBlockNotifier ram_notifier; |
|||
struct vfio_region_info config_region_info, bar_region_info[6]; |
|||
|
|||
/* These fields are protected by @lock */ |
|||
/* VFIO's IO virtual address space is managed by splitting into a few
|
|||
* sections: |
|||
* |
|||
* --------------- <= 0 |
|||
* |xxxxxxxxxxxxx| |
|||
* |-------------| <= QEMU_VFIO_IOVA_MIN |
|||
* | | |
|||
* | Fixed | |
|||
* | | |
|||
* |-------------| <= low_water_mark |
|||
* | | |
|||
* | Free | |
|||
* | | |
|||
* |-------------| <= high_water_mark |
|||
* | | |
|||
* | Temp | |
|||
* | | |
|||
* |-------------| <= QEMU_VFIO_IOVA_MAX |
|||
* |xxxxxxxxxxxxx| |
|||
* |xxxxxxxxxxxxx| |
|||
* --------------- |
|||
* |
|||
* - Addresses lower than QEMU_VFIO_IOVA_MIN are reserved as invalid; |
|||
* |
|||
* - Fixed mappings of HVAs are assigned "low" IOVAs in the range of |
|||
* [QEMU_VFIO_IOVA_MIN, low_water_mark). Once allocated they will not be |
|||
* reclaimed - low_water_mark never shrinks; |
|||
* |
|||
* - IOVAs in range [low_water_mark, high_water_mark) are free; |
|||
* |
|||
* - IOVAs in range [high_water_mark, QEMU_VFIO_IOVA_MAX) are volatile |
|||
* mappings. At each qemu_vfio_dma_reset_temporary() call, the whole area |
|||
* is recycled. The caller should make sure I/O's depending on these |
|||
* mappings are completed before calling. |
|||
**/ |
|||
uint64_t low_water_mark; |
|||
uint64_t high_water_mark; |
|||
IOVAMapping *mappings; |
|||
int nr_mappings; |
|||
}; |
|||
|
|||
/**
|
|||
* Find group file by PCI device address as specified @device, and return the |
|||
* path. The returned string is owned by caller and should be g_free'ed later. |
|||
*/ |
|||
static char *sysfs_find_group_file(const char *device, Error **errp) |
|||
{ |
|||
char *sysfs_link; |
|||
char *sysfs_group; |
|||
char *p; |
|||
char *path = NULL; |
|||
|
|||
sysfs_link = g_strdup_printf("/sys/bus/pci/devices/%s/iommu_group", device); |
|||
sysfs_group = g_malloc(PATH_MAX); |
|||
if (readlink(sysfs_link, sysfs_group, PATH_MAX - 1) == -1) { |
|||
error_setg_errno(errp, errno, "Failed to find iommu group sysfs path"); |
|||
goto out; |
|||
} |
|||
p = strrchr(sysfs_group, '/'); |
|||
if (!p) { |
|||
error_setg(errp, "Failed to find iommu group number"); |
|||
goto out; |
|||
} |
|||
|
|||
path = g_strdup_printf("/dev/vfio/%s", p + 1); |
|||
out: |
|||
g_free(sysfs_link); |
|||
g_free(sysfs_group); |
|||
return path; |
|||
} |
|||
|
|||
static inline void assert_bar_index_valid(QEMUVFIOState *s, int index) |
|||
{ |
|||
assert(index >= 0 && index < ARRAY_SIZE(s->bar_region_info)); |
|||
} |
|||
|
|||
static int qemu_vfio_pci_init_bar(QEMUVFIOState *s, int index, Error **errp) |
|||
{ |
|||
assert_bar_index_valid(s, index); |
|||
s->bar_region_info[index] = (struct vfio_region_info) { |
|||
.index = VFIO_PCI_BAR0_REGION_INDEX + index, |
|||
.argsz = sizeof(struct vfio_region_info), |
|||
}; |
|||
if (ioctl(s->device, VFIO_DEVICE_GET_REGION_INFO, &s->bar_region_info[index])) { |
|||
error_setg_errno(errp, errno, "Failed to get BAR region info"); |
|||
return -errno; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/**
|
|||
* Map a PCI bar area. |
|||
*/ |
|||
void *qemu_vfio_pci_map_bar(QEMUVFIOState *s, int index, |
|||
uint64_t offset, uint64_t size, |
|||
Error **errp) |
|||
{ |
|||
void *p; |
|||
assert_bar_index_valid(s, index); |
|||
p = mmap(NULL, MIN(size, s->bar_region_info[index].size - offset), |
|||
PROT_READ | PROT_WRITE, MAP_SHARED, |
|||
s->device, s->bar_region_info[index].offset + offset); |
|||
if (p == MAP_FAILED) { |
|||
error_setg_errno(errp, errno, "Failed to map BAR region"); |
|||
p = NULL; |
|||
} |
|||
return p; |
|||
} |
|||
|
|||
/**
|
|||
* Unmap a PCI bar area. |
|||
*/ |
|||
void qemu_vfio_pci_unmap_bar(QEMUVFIOState *s, int index, void *bar, |
|||
uint64_t offset, uint64_t size) |
|||
{ |
|||
if (bar) { |
|||
munmap(bar, MIN(size, s->bar_region_info[index].size - offset)); |
|||
} |
|||
} |
|||
|
|||
/**
|
|||
* Initialize device IRQ with @irq_type and and register an event notifier. |
|||
*/ |
|||
int qemu_vfio_pci_init_irq(QEMUVFIOState *s, EventNotifier *e, |
|||
int irq_type, Error **errp) |
|||
{ |
|||
int r; |
|||
struct vfio_irq_set *irq_set; |
|||
size_t irq_set_size; |
|||
struct vfio_irq_info irq_info = { .argsz = sizeof(irq_info) }; |
|||
|
|||
irq_info.index = irq_type; |
|||
if (ioctl(s->device, VFIO_DEVICE_GET_IRQ_INFO, &irq_info)) { |
|||
error_setg_errno(errp, errno, "Failed to get device interrupt info"); |
|||
return -errno; |
|||
} |
|||
if (!(irq_info.flags & VFIO_IRQ_INFO_EVENTFD)) { |
|||
error_setg(errp, "Device interrupt doesn't support eventfd"); |
|||
return -EINVAL; |
|||
} |
|||
|
|||
irq_set_size = sizeof(*irq_set) + sizeof(int); |
|||
irq_set = g_malloc0(irq_set_size); |
|||
|
|||
/* Get to a known IRQ state */ |
|||
*irq_set = (struct vfio_irq_set) { |
|||
.argsz = irq_set_size, |
|||
.flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER, |
|||
.index = irq_info.index, |
|||
.start = 0, |
|||
.count = 1, |
|||
}; |
|||
|
|||
*(int *)&irq_set->data = event_notifier_get_fd(e); |
|||
r = ioctl(s->device, VFIO_DEVICE_SET_IRQS, irq_set); |
|||
g_free(irq_set); |
|||
if (r) { |
|||
error_setg_errno(errp, errno, "Failed to setup device interrupt"); |
|||
return -errno; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
static int qemu_vfio_pci_read_config(QEMUVFIOState *s, void *buf, |
|||
int size, int ofs) |
|||
{ |
|||
int ret; |
|||
|
|||
do { |
|||
ret = pread(s->device, buf, size, s->config_region_info.offset + ofs); |
|||
} while (ret == -1 && errno == EINTR); |
|||
return ret == size ? 0 : -errno; |
|||
} |
|||
|
|||
static int qemu_vfio_pci_write_config(QEMUVFIOState *s, void *buf, int size, int ofs) |
|||
{ |
|||
int ret; |
|||
|
|||
do { |
|||
ret = pwrite(s->device, buf, size, s->config_region_info.offset + ofs); |
|||
} while (ret == -1 && errno == EINTR); |
|||
return ret == size ? 0 : -errno; |
|||
} |
|||
|
|||
static int qemu_vfio_init_pci(QEMUVFIOState *s, const char *device, |
|||
Error **errp) |
|||
{ |
|||
int ret; |
|||
int i; |
|||
uint16_t pci_cmd; |
|||
struct vfio_group_status group_status = { .argsz = sizeof(group_status) }; |
|||
struct vfio_iommu_type1_info iommu_info = { .argsz = sizeof(iommu_info) }; |
|||
struct vfio_device_info device_info = { .argsz = sizeof(device_info) }; |
|||
char *group_file = NULL; |
|||
|
|||
/* Create a new container */ |
|||
s->container = open("/dev/vfio/vfio", O_RDWR); |
|||
|
|||
if (s->container == -1) { |
|||
error_setg_errno(errp, errno, "Failed to open /dev/vfio/vfio"); |
|||
return -errno; |
|||
} |
|||
if (ioctl(s->container, VFIO_GET_API_VERSION) != VFIO_API_VERSION) { |
|||
error_setg(errp, "Invalid VFIO version"); |
|||
ret = -EINVAL; |
|||
goto fail_container; |
|||
} |
|||
|
|||
if (!ioctl(s->container, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU)) { |
|||
error_setg_errno(errp, errno, "VFIO IOMMU check failed"); |
|||
ret = -EINVAL; |
|||
goto fail_container; |
|||
} |
|||
|
|||
/* Open the group */ |
|||
group_file = sysfs_find_group_file(device, errp); |
|||
if (!group_file) { |
|||
ret = -EINVAL; |
|||
goto fail_container; |
|||
} |
|||
|
|||
s->group = open(group_file, O_RDWR); |
|||
if (s->group == -1) { |
|||
error_setg_errno(errp, errno, "Failed to open VFIO group file: %s", |
|||
group_file); |
|||
g_free(group_file); |
|||
ret = -errno; |
|||
goto fail_container; |
|||
} |
|||
g_free(group_file); |
|||
|
|||
/* Test the group is viable and available */ |
|||
if (ioctl(s->group, VFIO_GROUP_GET_STATUS, &group_status)) { |
|||
error_setg_errno(errp, errno, "Failed to get VFIO group status"); |
|||
ret = -errno; |
|||
goto fail; |
|||
} |
|||
|
|||
if (!(group_status.flags & VFIO_GROUP_FLAGS_VIABLE)) { |
|||
error_setg(errp, "VFIO group is not viable"); |
|||
ret = -EINVAL; |
|||
goto fail; |
|||
} |
|||
|
|||
/* Add the group to the container */ |
|||
if (ioctl(s->group, VFIO_GROUP_SET_CONTAINER, &s->container)) { |
|||
error_setg_errno(errp, errno, "Failed to add group to VFIO container"); |
|||
ret = -errno; |
|||
goto fail; |
|||
} |
|||
|
|||
/* Enable the IOMMU model we want */ |
|||
if (ioctl(s->container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU)) { |
|||
error_setg_errno(errp, errno, "Failed to set VFIO IOMMU type"); |
|||
ret = -errno; |
|||
goto fail; |
|||
} |
|||
|
|||
/* Get additional IOMMU info */ |
|||
if (ioctl(s->container, VFIO_IOMMU_GET_INFO, &iommu_info)) { |
|||
error_setg_errno(errp, errno, "Failed to get IOMMU info"); |
|||
ret = -errno; |
|||
goto fail; |
|||
} |
|||
|
|||
s->device = ioctl(s->group, VFIO_GROUP_GET_DEVICE_FD, device); |
|||
|
|||
if (s->device < 0) { |
|||
error_setg_errno(errp, errno, "Failed to get device fd"); |
|||
ret = -errno; |
|||
goto fail; |
|||
} |
|||
|
|||
/* Test and setup the device */ |
|||
if (ioctl(s->device, VFIO_DEVICE_GET_INFO, &device_info)) { |
|||
error_setg_errno(errp, errno, "Failed to get device info"); |
|||
ret = -errno; |
|||
goto fail; |
|||
} |
|||
|
|||
if (device_info.num_regions < VFIO_PCI_CONFIG_REGION_INDEX) { |
|||
error_setg(errp, "Invalid device regions"); |
|||
ret = -EINVAL; |
|||
goto fail; |
|||
} |
|||
|
|||
s->config_region_info = (struct vfio_region_info) { |
|||
.index = VFIO_PCI_CONFIG_REGION_INDEX, |
|||
.argsz = sizeof(struct vfio_region_info), |
|||
}; |
|||
if (ioctl(s->device, VFIO_DEVICE_GET_REGION_INFO, &s->config_region_info)) { |
|||
error_setg_errno(errp, errno, "Failed to get config region info"); |
|||
ret = -errno; |
|||
goto fail; |
|||
} |
|||
|
|||
for (i = 0; i < 6; i++) { |
|||
ret = qemu_vfio_pci_init_bar(s, i, errp); |
|||
if (ret) { |
|||
goto fail; |
|||
} |
|||
} |
|||
|
|||
/* Enable bus master */ |
|||
ret = qemu_vfio_pci_read_config(s, &pci_cmd, sizeof(pci_cmd), PCI_COMMAND); |
|||
if (ret) { |
|||
goto fail; |
|||
} |
|||
pci_cmd |= PCI_COMMAND_MASTER; |
|||
ret = qemu_vfio_pci_write_config(s, &pci_cmd, sizeof(pci_cmd), PCI_COMMAND); |
|||
if (ret) { |
|||
goto fail; |
|||
} |
|||
return 0; |
|||
fail: |
|||
close(s->group); |
|||
fail_container: |
|||
close(s->container); |
|||
return ret; |
|||
} |
|||
|
|||
static void qemu_vfio_ram_block_added(RAMBlockNotifier *n, |
|||
void *host, size_t size) |
|||
{ |
|||
QEMUVFIOState *s = container_of(n, QEMUVFIOState, ram_notifier); |
|||
trace_qemu_vfio_ram_block_added(s, host, size); |
|||
qemu_vfio_dma_map(s, host, size, false, NULL); |
|||
} |
|||
|
|||
static void qemu_vfio_ram_block_removed(RAMBlockNotifier *n, |
|||
void *host, size_t size) |
|||
{ |
|||
QEMUVFIOState *s = container_of(n, QEMUVFIOState, ram_notifier); |
|||
if (host) { |
|||
trace_qemu_vfio_ram_block_removed(s, host, size); |
|||
qemu_vfio_dma_unmap(s, host); |
|||
} |
|||
} |
|||
|
|||
static int qemu_vfio_init_ramblock(const char *block_name, void *host_addr, |
|||
ram_addr_t offset, ram_addr_t length, |
|||
void *opaque) |
|||
{ |
|||
int ret; |
|||
QEMUVFIOState *s = opaque; |
|||
|
|||
if (!host_addr) { |
|||
return 0; |
|||
} |
|||
ret = qemu_vfio_dma_map(s, host_addr, length, false, NULL); |
|||
if (ret) { |
|||
fprintf(stderr, "qemu_vfio_init_ramblock: failed %p %" PRId64 "\n", |
|||
host_addr, (uint64_t)length); |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
static void qemu_vfio_open_common(QEMUVFIOState *s) |
|||
{ |
|||
s->ram_notifier.ram_block_added = qemu_vfio_ram_block_added; |
|||
s->ram_notifier.ram_block_removed = qemu_vfio_ram_block_removed; |
|||
ram_block_notifier_add(&s->ram_notifier); |
|||
s->low_water_mark = QEMU_VFIO_IOVA_MIN; |
|||
s->high_water_mark = QEMU_VFIO_IOVA_MAX; |
|||
qemu_ram_foreach_block(qemu_vfio_init_ramblock, s); |
|||
qemu_mutex_init(&s->lock); |
|||
} |
|||
|
|||
/**
|
|||
* Open a PCI device, e.g. "0000:00:01.0". |
|||
*/ |
|||
QEMUVFIOState *qemu_vfio_open_pci(const char *device, Error **errp) |
|||
{ |
|||
int r; |
|||
QEMUVFIOState *s = g_new0(QEMUVFIOState, 1); |
|||
|
|||
r = qemu_vfio_init_pci(s, device, errp); |
|||
if (r) { |
|||
g_free(s); |
|||
return NULL; |
|||
} |
|||
qemu_vfio_open_common(s); |
|||
return s; |
|||
} |
|||
|
|||
static void qemu_vfio_dump_mapping(IOVAMapping *m) |
|||
{ |
|||
if (QEMU_VFIO_DEBUG) { |
|||
printf(" vfio mapping %p %" PRIx64 " to %" PRIx64 "\n", m->host, |
|||
(uint64_t)m->size, (uint64_t)m->iova); |
|||
} |
|||
} |
|||
|
|||
static void qemu_vfio_dump_mappings(QEMUVFIOState *s) |
|||
{ |
|||
int i; |
|||
|
|||
if (QEMU_VFIO_DEBUG) { |
|||
printf("vfio mappings\n"); |
|||
for (i = 0; i < s->nr_mappings; ++i) { |
|||
qemu_vfio_dump_mapping(&s->mappings[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/**
|
|||
* Find the mapping entry that contains [host, host + size) and set @index to |
|||
* the position. If no entry contains it, @index is the position _after_ which |
|||
* to insert the new mapping. IOW, it is the index of the largest element that |
|||
* is smaller than @host, or -1 if no entry is. |
|||
*/ |
|||
static IOVAMapping *qemu_vfio_find_mapping(QEMUVFIOState *s, void *host, |
|||
int *index) |
|||
{ |
|||
IOVAMapping *p = s->mappings; |
|||
IOVAMapping *q = p ? p + s->nr_mappings - 1 : NULL; |
|||
IOVAMapping *mid; |
|||
trace_qemu_vfio_find_mapping(s, host); |
|||
if (!p) { |
|||
*index = -1; |
|||
return NULL; |
|||
} |
|||
while (true) { |
|||
mid = p + (q - p) / 2; |
|||
if (mid == p) { |
|||
break; |
|||
} |
|||
if (mid->host > host) { |
|||
q = mid; |
|||
} else if (mid->host < host) { |
|||
p = mid; |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
if (mid->host > host) { |
|||
mid--; |
|||
} else if (mid < &s->mappings[s->nr_mappings - 1] |
|||
&& (mid + 1)->host <= host) { |
|||
mid++; |
|||
} |
|||
*index = mid - &s->mappings[0]; |
|||
if (mid >= &s->mappings[0] && |
|||
mid->host <= host && mid->host + mid->size > host) { |
|||
assert(mid < &s->mappings[s->nr_mappings]); |
|||
return mid; |
|||
} |
|||
/* At this point *index + 1 is the right position to insert the new
|
|||
* mapping.*/ |
|||
return NULL; |
|||
} |
|||
|
|||
/**
|
|||
* Allocate IOVA and and create a new mapping record and insert it in @s. |
|||
*/ |
|||
static IOVAMapping *qemu_vfio_add_mapping(QEMUVFIOState *s, |
|||
void *host, size_t size, |
|||
int index, uint64_t iova) |
|||
{ |
|||
int shift; |
|||
IOVAMapping m = {.host = host, .size = size, .iova = iova}; |
|||
IOVAMapping *insert; |
|||
|
|||
assert(QEMU_IS_ALIGNED(size, getpagesize())); |
|||
assert(QEMU_IS_ALIGNED(s->low_water_mark, getpagesize())); |
|||
assert(QEMU_IS_ALIGNED(s->high_water_mark, getpagesize())); |
|||
trace_qemu_vfio_new_mapping(s, host, size, index, iova); |
|||
|
|||
assert(index >= 0); |
|||
s->nr_mappings++; |
|||
s->mappings = g_realloc_n(s->mappings, sizeof(s->mappings[0]), |
|||
s->nr_mappings); |
|||
insert = &s->mappings[index]; |
|||
shift = s->nr_mappings - index - 1; |
|||
if (shift) { |
|||
memmove(insert + 1, insert, shift * sizeof(s->mappings[0])); |
|||
} |
|||
*insert = m; |
|||
return insert; |
|||
} |
|||
|
|||
/* Do the DMA mapping with VFIO. */ |
|||
static int qemu_vfio_do_mapping(QEMUVFIOState *s, void *host, size_t size, |
|||
uint64_t iova) |
|||
{ |
|||
struct vfio_iommu_type1_dma_map dma_map = { |
|||
.argsz = sizeof(dma_map), |
|||
.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE, |
|||
.iova = iova, |
|||
.vaddr = (uintptr_t)host, |
|||
.size = size, |
|||
}; |
|||
trace_qemu_vfio_do_mapping(s, host, size, iova); |
|||
|
|||
if (ioctl(s->container, VFIO_IOMMU_MAP_DMA, &dma_map)) { |
|||
error_report("VFIO_MAP_DMA: %d", -errno); |
|||
return -errno; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
/**
|
|||
* Undo the DMA mapping from @s with VFIO, and remove from mapping list. |
|||
*/ |
|||
static void qemu_vfio_undo_mapping(QEMUVFIOState *s, IOVAMapping *mapping, |
|||
Error **errp) |
|||
{ |
|||
int index; |
|||
struct vfio_iommu_type1_dma_unmap unmap = { |
|||
.argsz = sizeof(unmap), |
|||
.flags = 0, |
|||
.iova = mapping->iova, |
|||
.size = mapping->size, |
|||
}; |
|||
|
|||
index = mapping - s->mappings; |
|||
assert(mapping->size > 0); |
|||
assert(QEMU_IS_ALIGNED(mapping->size, getpagesize())); |
|||
assert(index >= 0 && index < s->nr_mappings); |
|||
if (ioctl(s->container, VFIO_IOMMU_UNMAP_DMA, &unmap)) { |
|||
error_setg(errp, "VFIO_UNMAP_DMA failed: %d", -errno); |
|||
} |
|||
memmove(mapping, &s->mappings[index + 1], |
|||
sizeof(s->mappings[0]) * (s->nr_mappings - index - 1)); |
|||
s->nr_mappings--; |
|||
s->mappings = g_realloc_n(s->mappings, sizeof(s->mappings[0]), |
|||
s->nr_mappings); |
|||
} |
|||
|
|||
/* Check if the mapping list is (ascending) ordered. */ |
|||
static bool qemu_vfio_verify_mappings(QEMUVFIOState *s) |
|||
{ |
|||
int i; |
|||
if (QEMU_VFIO_DEBUG) { |
|||
for (i = 0; i < s->nr_mappings - 1; ++i) { |
|||
if (!(s->mappings[i].host < s->mappings[i + 1].host)) { |
|||
fprintf(stderr, "item %d not sorted!\n", i); |
|||
qemu_vfio_dump_mappings(s); |
|||
return false; |
|||
} |
|||
if (!(s->mappings[i].host + s->mappings[i].size <= |
|||
s->mappings[i + 1].host)) { |
|||
fprintf(stderr, "item %d overlap with next!\n", i); |
|||
qemu_vfio_dump_mappings(s); |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
/* Map [host, host + size) area into a contiguous IOVA address space, and store
|
|||
* the result in @iova if not NULL. The caller need to make sure the area is |
|||
* aligned to page size, and mustn't overlap with existing mapping areas (split |
|||
* mapping status within this area is not allowed). |
|||
*/ |
|||
int qemu_vfio_dma_map(QEMUVFIOState *s, void *host, size_t size, |
|||
bool temporary, uint64_t *iova) |
|||
{ |
|||
int ret = 0; |
|||
int index; |
|||
IOVAMapping *mapping; |
|||
uint64_t iova0; |
|||
|
|||
assert(QEMU_PTR_IS_ALIGNED(host, getpagesize())); |
|||
assert(QEMU_IS_ALIGNED(size, getpagesize())); |
|||
trace_qemu_vfio_dma_map(s, host, size, temporary, iova); |
|||
qemu_mutex_lock(&s->lock); |
|||
mapping = qemu_vfio_find_mapping(s, host, &index); |
|||
if (mapping) { |
|||
iova0 = mapping->iova + ((uint8_t *)host - (uint8_t *)mapping->host); |
|||
} else { |
|||
if (s->high_water_mark - s->low_water_mark + 1 < size) { |
|||
ret = -ENOMEM; |
|||
goto out; |
|||
} |
|||
if (!temporary) { |
|||
iova0 = s->low_water_mark; |
|||
mapping = qemu_vfio_add_mapping(s, host, size, index + 1, iova0); |
|||
if (!mapping) { |
|||
ret = -ENOMEM; |
|||
goto out; |
|||
} |
|||
assert(qemu_vfio_verify_mappings(s)); |
|||
ret = qemu_vfio_do_mapping(s, host, size, iova0); |
|||
if (ret) { |
|||
qemu_vfio_undo_mapping(s, mapping, NULL); |
|||
goto out; |
|||
} |
|||
s->low_water_mark += size; |
|||
qemu_vfio_dump_mappings(s); |
|||
} else { |
|||
iova0 = s->high_water_mark - size; |
|||
ret = qemu_vfio_do_mapping(s, host, size, iova0); |
|||
if (ret) { |
|||
goto out; |
|||
} |
|||
s->high_water_mark -= size; |
|||
} |
|||
} |
|||
if (iova) { |
|||
*iova = iova0; |
|||
} |
|||
out: |
|||
qemu_mutex_unlock(&s->lock); |
|||
return ret; |
|||
} |
|||
|
|||
/* Reset the high watermark and free all "temporary" mappings. */ |
|||
int qemu_vfio_dma_reset_temporary(QEMUVFIOState *s) |
|||
{ |
|||
struct vfio_iommu_type1_dma_unmap unmap = { |
|||
.argsz = sizeof(unmap), |
|||
.flags = 0, |
|||
.iova = s->high_water_mark, |
|||
.size = QEMU_VFIO_IOVA_MAX - s->high_water_mark, |
|||
}; |
|||
trace_qemu_vfio_dma_reset_temporary(s); |
|||
qemu_mutex_lock(&s->lock); |
|||
if (ioctl(s->container, VFIO_IOMMU_UNMAP_DMA, &unmap)) { |
|||
error_report("VFIO_UNMAP_DMA: %d", -errno); |
|||
qemu_mutex_unlock(&s->lock); |
|||
return -errno; |
|||
} |
|||
s->high_water_mark = QEMU_VFIO_IOVA_MAX; |
|||
qemu_mutex_unlock(&s->lock); |
|||
return 0; |
|||
} |
|||
|
|||
/* Unmapping the whole area that was previously mapped with
|
|||
* qemu_vfio_dma_map(). */ |
|||
void qemu_vfio_dma_unmap(QEMUVFIOState *s, void *host) |
|||
{ |
|||
int index = 0; |
|||
IOVAMapping *m; |
|||
|
|||
if (!host) { |
|||
return; |
|||
} |
|||
|
|||
trace_qemu_vfio_dma_unmap(s, host); |
|||
qemu_mutex_lock(&s->lock); |
|||
m = qemu_vfio_find_mapping(s, host, &index); |
|||
if (!m) { |
|||
goto out; |
|||
} |
|||
qemu_vfio_undo_mapping(s, m, NULL); |
|||
out: |
|||
qemu_mutex_unlock(&s->lock); |
|||
} |
|||
|
|||
static void qemu_vfio_reset(QEMUVFIOState *s) |
|||
{ |
|||
ioctl(s->device, VFIO_DEVICE_RESET); |
|||
} |
|||
|
|||
/* Close and free the VFIO resources. */ |
|||
void qemu_vfio_close(QEMUVFIOState *s) |
|||
{ |
|||
int i; |
|||
|
|||
if (!s) { |
|||
return; |
|||
} |
|||
for (i = 0; i < s->nr_mappings; ++i) { |
|||
qemu_vfio_undo_mapping(s, &s->mappings[i], NULL); |
|||
} |
|||
ram_block_notifier_remove(&s->ram_notifier); |
|||
qemu_vfio_reset(s); |
|||
close(s->device); |
|||
close(s->group); |
|||
close(s->container); |
|||
} |
|||
Loading…
Reference in new issue