mirror of https://github.com/xemu-project/xemu.git
vfio-user: handle PCI BAR accesses
Determine the BARs used by the PCI device and register handlers to manage the access to the same. Signed-off-by: Elena Ufimtseva <elena.ufimtseva@oracle.com> Signed-off-by: John G Johnson <john.g.johnson@oracle.com> Signed-off-by: Jagannathan Raman <jag.raman@oracle.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Message-id: 3373e10b5be5f42846f0632d4382466e1698c505.1655151679.git.jag.raman@oracle.com Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
parent
15ccf9bee7
commit
3123f93d6b
|
@ -9,3 +9,6 @@ vfu_cfg_read(uint32_t offset, uint32_t val) "vfu: cfg: 0x%u -> 0x%x"
|
||||||
vfu_cfg_write(uint32_t offset, uint32_t val) "vfu: cfg: 0x%u <- 0x%x"
|
vfu_cfg_write(uint32_t offset, uint32_t val) "vfu: cfg: 0x%u <- 0x%x"
|
||||||
vfu_dma_register(uint64_t gpa, size_t len) "vfu: registering GPA 0x%"PRIx64", %zu bytes"
|
vfu_dma_register(uint64_t gpa, size_t len) "vfu: registering GPA 0x%"PRIx64", %zu bytes"
|
||||||
vfu_dma_unregister(uint64_t gpa) "vfu: unregistering GPA 0x%"PRIx64""
|
vfu_dma_unregister(uint64_t gpa) "vfu: unregistering GPA 0x%"PRIx64""
|
||||||
|
vfu_bar_register(int i, uint64_t addr, uint64_t size) "vfu: BAR %d: addr 0x%"PRIx64" size 0x%"PRIx64""
|
||||||
|
vfu_bar_rw_enter(const char *op, uint64_t addr) "vfu: %s request for BAR address 0x%"PRIx64""
|
||||||
|
vfu_bar_rw_exit(const char *op, uint64_t addr) "vfu: Finished %s of BAR address 0x%"PRIx64""
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
#include "hw/qdev-core.h"
|
#include "hw/qdev-core.h"
|
||||||
#include "hw/pci/pci.h"
|
#include "hw/pci/pci.h"
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
|
#include "exec/memory.h"
|
||||||
|
|
||||||
#define TYPE_VFU_OBJECT "x-vfio-user-server"
|
#define TYPE_VFU_OBJECT "x-vfio-user-server"
|
||||||
OBJECT_DECLARE_TYPE(VfuObject, VfuObjectClass, VFU_OBJECT)
|
OBJECT_DECLARE_TYPE(VfuObject, VfuObjectClass, VFU_OBJECT)
|
||||||
|
@ -332,6 +333,193 @@ static void dma_unregister(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info)
|
||||||
trace_vfu_dma_unregister((uint64_t)info->iova.iov_base);
|
trace_vfu_dma_unregister((uint64_t)info->iova.iov_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int vfu_object_mr_rw(MemoryRegion *mr, uint8_t *buf, hwaddr offset,
|
||||||
|
hwaddr size, const bool is_write)
|
||||||
|
{
|
||||||
|
uint8_t *ptr = buf;
|
||||||
|
bool release_lock = false;
|
||||||
|
uint8_t *ram_ptr = NULL;
|
||||||
|
MemTxResult result;
|
||||||
|
int access_size;
|
||||||
|
uint64_t val;
|
||||||
|
|
||||||
|
if (memory_access_is_direct(mr, is_write)) {
|
||||||
|
/**
|
||||||
|
* Some devices expose a PCI expansion ROM, which could be buffer
|
||||||
|
* based as compared to other regions which are primarily based on
|
||||||
|
* MemoryRegionOps. memory_region_find() would already check
|
||||||
|
* for buffer overflow, we don't need to repeat it here.
|
||||||
|
*/
|
||||||
|
ram_ptr = memory_region_get_ram_ptr(mr);
|
||||||
|
|
||||||
|
if (is_write) {
|
||||||
|
memcpy((ram_ptr + offset), buf, size);
|
||||||
|
} else {
|
||||||
|
memcpy(buf, (ram_ptr + offset), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (size) {
|
||||||
|
/**
|
||||||
|
* The read/write logic used below is similar to the ones in
|
||||||
|
* flatview_read/write_continue()
|
||||||
|
*/
|
||||||
|
release_lock = prepare_mmio_access(mr);
|
||||||
|
|
||||||
|
access_size = memory_access_size(mr, size, offset);
|
||||||
|
|
||||||
|
if (is_write) {
|
||||||
|
val = ldn_he_p(ptr, access_size);
|
||||||
|
|
||||||
|
result = memory_region_dispatch_write(mr, offset, val,
|
||||||
|
size_memop(access_size),
|
||||||
|
MEMTXATTRS_UNSPECIFIED);
|
||||||
|
} else {
|
||||||
|
result = memory_region_dispatch_read(mr, offset, &val,
|
||||||
|
size_memop(access_size),
|
||||||
|
MEMTXATTRS_UNSPECIFIED);
|
||||||
|
|
||||||
|
stn_he_p(ptr, access_size, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (release_lock) {
|
||||||
|
qemu_mutex_unlock_iothread();
|
||||||
|
release_lock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != MEMTX_OK) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size -= access_size;
|
||||||
|
ptr += access_size;
|
||||||
|
offset += access_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t vfu_object_bar_rw(PCIDevice *pci_dev, int pci_bar,
|
||||||
|
hwaddr bar_offset, char * const buf,
|
||||||
|
hwaddr len, const bool is_write)
|
||||||
|
{
|
||||||
|
MemoryRegionSection section = { 0 };
|
||||||
|
uint8_t *ptr = (uint8_t *)buf;
|
||||||
|
MemoryRegion *section_mr = NULL;
|
||||||
|
uint64_t section_size;
|
||||||
|
hwaddr section_offset;
|
||||||
|
hwaddr size = 0;
|
||||||
|
|
||||||
|
while (len) {
|
||||||
|
section = memory_region_find(pci_dev->io_regions[pci_bar].memory,
|
||||||
|
bar_offset, len);
|
||||||
|
|
||||||
|
if (!section.mr) {
|
||||||
|
warn_report("vfu: invalid address 0x%"PRIx64"", bar_offset);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
section_mr = section.mr;
|
||||||
|
section_offset = section.offset_within_region;
|
||||||
|
section_size = int128_get64(section.size);
|
||||||
|
|
||||||
|
if (is_write && section_mr->readonly) {
|
||||||
|
warn_report("vfu: attempting to write to readonly region in "
|
||||||
|
"bar %d - [0x%"PRIx64" - 0x%"PRIx64"]",
|
||||||
|
pci_bar, bar_offset,
|
||||||
|
(bar_offset + section_size));
|
||||||
|
memory_region_unref(section_mr);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vfu_object_mr_rw(section_mr, ptr, section_offset,
|
||||||
|
section_size, is_write)) {
|
||||||
|
warn_report("vfu: failed to %s "
|
||||||
|
"[0x%"PRIx64" - 0x%"PRIx64"] in bar %d",
|
||||||
|
is_write ? "write to" : "read from", bar_offset,
|
||||||
|
(bar_offset + section_size), pci_bar);
|
||||||
|
memory_region_unref(section_mr);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size += section_size;
|
||||||
|
bar_offset += section_size;
|
||||||
|
ptr += section_size;
|
||||||
|
len -= section_size;
|
||||||
|
|
||||||
|
memory_region_unref(section_mr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VFU_OBJECT_BAR_HANDLER - macro for defining handlers for PCI BARs.
|
||||||
|
*
|
||||||
|
* To create handler for BAR number 2, VFU_OBJECT_BAR_HANDLER(2) would
|
||||||
|
* define vfu_object_bar2_handler
|
||||||
|
*/
|
||||||
|
#define VFU_OBJECT_BAR_HANDLER(BAR_NO) \
|
||||||
|
static ssize_t vfu_object_bar##BAR_NO##_handler(vfu_ctx_t *vfu_ctx, \
|
||||||
|
char * const buf, size_t count, \
|
||||||
|
loff_t offset, const bool is_write) \
|
||||||
|
{ \
|
||||||
|
VfuObject *o = vfu_get_private(vfu_ctx); \
|
||||||
|
PCIDevice *pci_dev = o->pci_dev; \
|
||||||
|
\
|
||||||
|
return vfu_object_bar_rw(pci_dev, BAR_NO, offset, \
|
||||||
|
buf, count, is_write); \
|
||||||
|
} \
|
||||||
|
|
||||||
|
VFU_OBJECT_BAR_HANDLER(0)
|
||||||
|
VFU_OBJECT_BAR_HANDLER(1)
|
||||||
|
VFU_OBJECT_BAR_HANDLER(2)
|
||||||
|
VFU_OBJECT_BAR_HANDLER(3)
|
||||||
|
VFU_OBJECT_BAR_HANDLER(4)
|
||||||
|
VFU_OBJECT_BAR_HANDLER(5)
|
||||||
|
VFU_OBJECT_BAR_HANDLER(6)
|
||||||
|
|
||||||
|
static vfu_region_access_cb_t *vfu_object_bar_handlers[PCI_NUM_REGIONS] = {
|
||||||
|
&vfu_object_bar0_handler,
|
||||||
|
&vfu_object_bar1_handler,
|
||||||
|
&vfu_object_bar2_handler,
|
||||||
|
&vfu_object_bar3_handler,
|
||||||
|
&vfu_object_bar4_handler,
|
||||||
|
&vfu_object_bar5_handler,
|
||||||
|
&vfu_object_bar6_handler,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vfu_object_register_bars - Identify active BAR regions of pdev and setup
|
||||||
|
* callbacks to handle read/write accesses
|
||||||
|
*/
|
||||||
|
static void vfu_object_register_bars(vfu_ctx_t *vfu_ctx, PCIDevice *pdev)
|
||||||
|
{
|
||||||
|
int flags = VFU_REGION_FLAG_RW;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < PCI_NUM_REGIONS; i++) {
|
||||||
|
if (!pdev->io_regions[i].size) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((i == VFU_PCI_DEV_ROM_REGION_IDX) ||
|
||||||
|
pdev->io_regions[i].memory->readonly) {
|
||||||
|
flags &= ~VFU_REGION_FLAG_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR0_REGION_IDX + i,
|
||||||
|
(size_t)pdev->io_regions[i].size,
|
||||||
|
vfu_object_bar_handlers[i],
|
||||||
|
flags, NULL, 0, -1, 0);
|
||||||
|
|
||||||
|
trace_vfu_bar_register(i, pdev->io_regions[i].addr,
|
||||||
|
pdev->io_regions[i].size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TYPE_VFU_OBJECT depends on the availability of the 'socket' and 'device'
|
* TYPE_VFU_OBJECT depends on the availability of the 'socket' and 'device'
|
||||||
* properties. It also depends on devices instantiated in QEMU. These
|
* properties. It also depends on devices instantiated in QEMU. These
|
||||||
|
@ -442,6 +630,8 @@ static void vfu_object_init_ctx(VfuObject *o, Error **errp)
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vfu_object_register_bars(o->vfu_ctx, o->pci_dev);
|
||||||
|
|
||||||
ret = vfu_realize_ctx(o->vfu_ctx);
|
ret = vfu_realize_ctx(o->vfu_ctx);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_setg(errp, "vfu: Failed to realize device %s- %s",
|
error_setg(errp, "vfu: Failed to realize device %s- %s",
|
||||||
|
|
|
@ -2810,6 +2810,9 @@ MemTxResult address_space_write_cached_slow(MemoryRegionCache *cache,
|
||||||
hwaddr addr, const void *buf,
|
hwaddr addr, const void *buf,
|
||||||
hwaddr len);
|
hwaddr len);
|
||||||
|
|
||||||
|
int memory_access_size(MemoryRegion *mr, unsigned l, hwaddr addr);
|
||||||
|
bool prepare_mmio_access(MemoryRegion *mr);
|
||||||
|
|
||||||
static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write)
|
static inline bool memory_access_is_direct(MemoryRegion *mr, bool is_write)
|
||||||
{
|
{
|
||||||
if (is_write) {
|
if (is_write) {
|
||||||
|
|
|
@ -2719,7 +2719,7 @@ void memory_region_flush_rom_device(MemoryRegion *mr, hwaddr addr, hwaddr size)
|
||||||
invalidate_and_set_dirty(mr, addr, size);
|
invalidate_and_set_dirty(mr, addr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int memory_access_size(MemoryRegion *mr, unsigned l, hwaddr addr)
|
int memory_access_size(MemoryRegion *mr, unsigned l, hwaddr addr)
|
||||||
{
|
{
|
||||||
unsigned access_size_max = mr->ops->valid.max_access_size;
|
unsigned access_size_max = mr->ops->valid.max_access_size;
|
||||||
|
|
||||||
|
@ -2746,7 +2746,7 @@ static int memory_access_size(MemoryRegion *mr, unsigned l, hwaddr addr)
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool prepare_mmio_access(MemoryRegion *mr)
|
bool prepare_mmio_access(MemoryRegion *mr)
|
||||||
{
|
{
|
||||||
bool release_lock = false;
|
bool release_lock = false;
|
||||||
|
|
||||||
|
|
|
@ -144,7 +144,7 @@ static void *pattern_alloc(pattern p, size_t len)
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int memory_access_size(MemoryRegion *mr, unsigned l, hwaddr addr)
|
static int fuzz_memory_access_size(MemoryRegion *mr, unsigned l, hwaddr addr)
|
||||||
{
|
{
|
||||||
unsigned access_size_max = mr->ops->valid.max_access_size;
|
unsigned access_size_max = mr->ops->valid.max_access_size;
|
||||||
|
|
||||||
|
@ -242,11 +242,12 @@ void fuzz_dma_read_cb(size_t addr, size_t len, MemoryRegion *mr)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If mr1 isn't RAM, address_space_translate doesn't update l. Use
|
* If mr1 isn't RAM, address_space_translate doesn't update l. Use
|
||||||
* memory_access_size to identify the number of bytes that it is safe
|
* fuzz_memory_access_size to identify the number of bytes that it
|
||||||
* to write without accidentally writing to another MemoryRegion.
|
* is safe to write without accidentally writing to another
|
||||||
|
* MemoryRegion.
|
||||||
*/
|
*/
|
||||||
if (!memory_region_is_ram(mr1)) {
|
if (!memory_region_is_ram(mr1)) {
|
||||||
l = memory_access_size(mr1, l, addr1);
|
l = fuzz_memory_access_size(mr1, l, addr1);
|
||||||
}
|
}
|
||||||
if (memory_region_is_ram(mr1) ||
|
if (memory_region_is_ram(mr1) ||
|
||||||
memory_region_is_romd(mr1) ||
|
memory_region_is_romd(mr1) ||
|
||||||
|
|
Loading…
Reference in New Issue