acrn-hypervisor/hypervisor/dm/io_req.c
Jian Jun Chen 74bc2f7cfb hv: asyncio: support data match of the same addr
Virtio legacy device (ver < 1.0) uses a single PIO for all virtqueues.
Notifications from different virtqueues are implemented by writing
virtqueue index to the PIO. Writing different values to the same addr
needs to be mapped to different eventfds by asyncio. This is called
data match feature of asyncio.

v3 -> v4:
 * Update the definition of `struct asyncio_desc`
   Use `struct acrn_asyncio_info` inside it, instaed of defining the duplicated
   fileds.
 * Update `add_asyncio` to use `memcpy_s` rather than assigning all the fields
   using 5 assignment statements.
 * Update `asyncio_is_conflict` for coding style
   120-character line is sufficient to write all conditions.
 * Update the checks related to `wildcard`
   Because we require every conditional clause to have a Boolean type
   in the coding guideline.

v2 -> v3:
No change

v1 -> v2:
No change

Tracked-On: #8612

Signed-off-by: Jian Jun Chen <jian.jun.chen@intel.com>
Signed-off-by: Shiqing Gao <shiqing.gao@intel.com>
Acked-by: Wang, Yu1 <yu1.wang@intel.com>
2024-06-05 15:23:33 +08:00

924 lines
25 KiB
C

/*
* Copyright (C) 2019-2022 Intel Corporation.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <asm/guest/vm.h>
#include <asm/irq.h>
#include <errno.h>
#include <logmsg.h>
#include <sbuf.h>
#define DBG_LEVEL_IOREQ 6U
static uint32_t acrn_hsm_notification_vector = HYPERVISOR_CALLBACK_HSM_VECTOR;
#define MMIO_DEFAULT_VALUE_SIZE_1 (0xFFUL)
#define MMIO_DEFAULT_VALUE_SIZE_2 (0xFFFFUL)
#define MMIO_DEFAULT_VALUE_SIZE_4 (0xFFFFFFFFUL)
#define MMIO_DEFAULT_VALUE_SIZE_8 (0xFFFFFFFFFFFFFFFFUL)
#if defined(HV_DEBUG)
__unused static void acrn_print_request(uint16_t vcpu_id, const struct acrn_io_request *req)
{
switch (req->type) {
case ACRN_IOREQ_TYPE_MMIO:
dev_dbg(DBG_LEVEL_IOREQ, "[vcpu_id=%hu type=MMIO]", vcpu_id);
dev_dbg(DBG_LEVEL_IOREQ,
"gpa=0x%lx, R/W=%d, size=%ld value=0x%lx processed=%lx",
req->reqs.mmio_request.address,
req->reqs.mmio_request.direction,
req->reqs.mmio_request.size,
req->reqs.mmio_request.value,
req->processed);
break;
case ACRN_IOREQ_TYPE_PORTIO:
dev_dbg(DBG_LEVEL_IOREQ, "[vcpu_id=%hu type=PORTIO]", vcpu_id);
dev_dbg(DBG_LEVEL_IOREQ,
"IO=0x%lx, R/W=%d, size=%ld value=0x%lx processed=%lx",
req->reqs.pio_request.address,
req->reqs.pio_request.direction,
req->reqs.pio_request.size,
req->reqs.pio_request.value,
req->processed);
break;
default:
dev_dbg(DBG_LEVEL_IOREQ, "[vcpu_id=%hu type=%d] NOT support type",
vcpu_id, req->type);
break;
}
}
#endif
/**
* @brief Reset all IO requests status of the VM
*
* @param vm The VM whose IO requests to be reset
*/
void reset_vm_ioreqs(struct acrn_vm *vm)
{
uint16_t i;
for (i = 0U; i < ACRN_IO_REQUEST_MAX; i++) {
set_io_req_state(vm, i, ACRN_IOREQ_STATE_FREE);
}
}
/**
* @pre vm->asyncio_lock is held
*/
static bool asyncio_is_conflict(struct acrn_vm *vm,
const struct acrn_asyncio_info *async_info)
{
struct list_head *pos;
struct asyncio_desc *p;
struct acrn_asyncio_info *info;
bool ret = false;
/* When either one's match_data is 0, the data matching will be skipped. */
list_for_each(pos, &vm->aiodesc_queue) {
p = container_of(pos, struct asyncio_desc, list);
info = &(p->asyncio_info);
if ((info->addr == async_info->addr) &&
(info->type == async_info->type) &&
((info->match_data == 0U) || (async_info->match_data == 0U) ||
(info->data == async_info->data))) {
ret = true;
break;
}
}
return ret;
}
int add_asyncio(struct acrn_vm *vm, const struct acrn_asyncio_info *async_info)
{
uint32_t i;
int ret = -1;
bool b_conflict;
struct asyncio_desc *desc;
if (async_info->addr != 0UL) {
spinlock_obtain(&vm->asyncio_lock);
b_conflict = asyncio_is_conflict(vm, async_info);
if (!b_conflict) {
for (i = 0U; i < ACRN_ASYNCIO_MAX; i++) {
desc = &(vm->aio_desc[i]);
if ((desc->asyncio_info.addr == 0UL) && (desc->asyncio_info.fd == 0UL)) {
(void)memcpy_s(&(desc->asyncio_info), sizeof(struct acrn_asyncio_info),
async_info, sizeof(struct acrn_asyncio_info));
INIT_LIST_HEAD(&(desc->list));
list_add(&(desc->list), &vm->aiodesc_queue);
ret = 0;
break;
}
}
spinlock_release(&vm->asyncio_lock);
if (i == ACRN_ASYNCIO_MAX) {
pr_fatal("too much fastio, would not support!");
}
} else {
spinlock_release(&vm->asyncio_lock);
pr_err("%s, already registered!", __func__);
}
} else {
pr_err("%s: base = 0 is not supported!", __func__);
}
return ret;
}
int remove_asyncio(struct acrn_vm *vm, const struct acrn_asyncio_info *async_info)
{
uint32_t i;
int ret = -1;
struct asyncio_desc *desc;
struct acrn_asyncio_info *info;
if (async_info->addr != 0UL) {
spinlock_obtain(&vm->asyncio_lock);
for (i = 0U; i < ACRN_ASYNCIO_MAX; i++) {
desc = &(vm->aio_desc[i]);
info = &(desc->asyncio_info);
if ((info->type == async_info->type)
&& (info->addr == async_info->addr)
&& (info->fd == async_info->fd)
&& ((info->match_data == 0U) == (async_info->match_data == 0U))
&& (info->data == async_info->data)) {
list_del_init(&(desc->list));
memset(desc, 0, sizeof(vm->aio_desc[0]));
ret = 0;
break;
}
}
spinlock_release(&vm->asyncio_lock);
if (i == ACRN_ASYNCIO_MAX) {
pr_fatal("Failed to find asyncio req on addr: %lx!", async_info->addr);
}
} else {
pr_err("%s: base = 0 is not supported!", __func__);
}
return ret;
}
static inline bool has_complete_ioreq(const struct acrn_vcpu *vcpu)
{
return (get_io_req_state(vcpu->vm, vcpu->vcpu_id) == ACRN_IOREQ_STATE_COMPLETE);
}
static struct asyncio_desc *get_asyncio_desc(struct acrn_vcpu *vcpu, const struct io_request *io_req)
{
uint64_t addr = 0UL;
uint32_t type;
uint64_t value;
struct list_head *pos;
struct asyncio_desc *iter_desc;
struct acrn_asyncio_info *iter_info;
struct acrn_vm *vm = vcpu->vm;
struct asyncio_desc *ret = NULL;
struct shared_buf *sbuf =
(struct shared_buf *)vm->sw.asyncio_sbuf;
if (sbuf != NULL) {
switch (io_req->io_type) {
case ACRN_IOREQ_TYPE_PORTIO:
addr = io_req->reqs.pio_request.address;
value = io_req->reqs.pio_request.value;
type = ACRN_ASYNCIO_PIO;
break;
case ACRN_IOREQ_TYPE_MMIO:
addr = io_req->reqs.mmio_request.address;
value = io_req->reqs.mmio_request.value;
type = ACRN_ASYNCIO_MMIO;
break;
default:
break;
}
if (addr != 0UL) {
spinlock_obtain(&vm->asyncio_lock);
list_for_each(pos, &vm->aiodesc_queue) {
iter_desc = container_of(pos, struct asyncio_desc, list);
iter_info = &(iter_desc->asyncio_info);
if ((iter_info->addr == addr) && (iter_info->type == type) &&
((iter_info->match_data == 0U) || (iter_info->data == value))) {
ret = iter_desc;
break;
}
}
spinlock_release(&vm->asyncio_lock);
}
}
return ret;
}
static int acrn_insert_asyncio(struct acrn_vcpu *vcpu, const uint64_t asyncio_fd)
{
struct acrn_vm *vm = vcpu->vm;
struct shared_buf *sbuf =
(struct shared_buf *)vm->sw.asyncio_sbuf;
int ret = -ENODEV;
if (sbuf != NULL) {
spinlock_obtain(&vm->asyncio_lock);
while (sbuf_put(sbuf, (uint8_t *)&asyncio_fd, sizeof(asyncio_fd)) == 0U) {
/* sbuf is full, try later.. */
spinlock_release(&vm->asyncio_lock);
asm_pause();
if (need_reschedule(pcpuid_from_vcpu(vcpu))) {
schedule();
}
spinlock_obtain(&vm->asyncio_lock);
}
spinlock_release(&vm->asyncio_lock);
arch_fire_hsm_interrupt();
ret = 0;
}
return ret;
}
/**
* @brief Deliver \p io_req to Service VM and suspend \p vcpu till its completion
*
* @param vcpu The virtual CPU that triggers the MMIO access
* @param io_req The I/O request holding the details of the MMIO access
*
* @pre vcpu != NULL && io_req != NULL
*/
int32_t acrn_insert_request(struct acrn_vcpu *vcpu, const struct io_request *io_req)
{
struct acrn_io_request_buffer *req_buf = NULL;
struct acrn_io_request *acrn_io_req;
bool is_polling = false;
int32_t ret = 0;
uint16_t cur;
if ((vcpu->vm->sw.io_shared_page != NULL)
&& (get_io_req_state(vcpu->vm, vcpu->vcpu_id) == ACRN_IOREQ_STATE_FREE)) {
req_buf = (struct acrn_io_request_buffer *)(vcpu->vm->sw.io_shared_page);
cur = vcpu->vcpu_id;
stac();
acrn_io_req = &req_buf->req_slot[cur];
/* ACRN insert request to HSM and inject upcall */
acrn_io_req->type = io_req->io_type;
(void)memcpy_s(&acrn_io_req->reqs, sizeof(acrn_io_req->reqs),
&io_req->reqs, sizeof(acrn_io_req->reqs));
if (vcpu->vm->sw.is_polling_ioreq) {
acrn_io_req->completion_polling = 1U;
is_polling = true;
}
clac();
/* Before updating the acrn_io_req state, enforce all fill acrn_io_req operations done */
cpu_write_memory_barrier();
/* Must clear the signal before we mark req as pending
* Once we mark it pending, HSM may process req and signal us
* before we perform upcall.
* because HSM can work in pulling mode without wait for upcall
*/
set_io_req_state(vcpu->vm, vcpu->vcpu_id, ACRN_IOREQ_STATE_PENDING);
/* signal HSM */
arch_fire_hsm_interrupt();
/* Polling completion of the request in polling mode */
if (is_polling) {
while (true) {
if (has_complete_ioreq(vcpu)) {
/* we have completed ioreq pending */
break;
}
asm_pause();
if (need_reschedule(pcpuid_from_vcpu(vcpu))) {
schedule();
}
}
} else {
wait_event(&vcpu->events[VCPU_EVENT_IOREQ]);
}
} else {
ret = -EINVAL;
}
return ret;
}
uint32_t get_io_req_state(struct acrn_vm *vm, uint16_t vcpu_id)
{
uint32_t state;
struct acrn_io_request_buffer *req_buf = NULL;
struct acrn_io_request *acrn_io_req;
req_buf = (struct acrn_io_request_buffer *)vm->sw.io_shared_page;
if (req_buf == NULL) {
state = 0xffffffffU;
} else {
stac();
acrn_io_req = &req_buf->req_slot[vcpu_id];
state = acrn_io_req->processed;
clac();
}
return state;
}
void set_io_req_state(struct acrn_vm *vm, uint16_t vcpu_id, uint32_t state)
{
struct acrn_io_request_buffer *req_buf = NULL;
struct acrn_io_request *acrn_io_req;
req_buf = (struct acrn_io_request_buffer *)vm->sw.io_shared_page;
if (req_buf != NULL) {
stac();
acrn_io_req = &req_buf->req_slot[vcpu_id];
/*
* HV will only set processed to ACRN_IOREQ_STATE_PENDING or ACRN_IOREQ_STATE_FREE.
* we don't need to sfence here is that even if the Service-VM/DM sees the previous state,
* the only side effect is that it will defer the processing of the new IOReq.
* It won't lead wrong processing.
*/
acrn_io_req->processed = state;
clac();
}
}
int init_asyncio(struct acrn_vm *vm, uint64_t *hva)
{
struct shared_buf *sbuf = (struct shared_buf *)hva;
int ret = -1;
stac();
if (sbuf != NULL) {
if (sbuf->magic == SBUF_MAGIC) {
vm->sw.asyncio_sbuf = sbuf;
INIT_LIST_HEAD(&vm->aiodesc_queue);
spinlock_init(&vm->asyncio_lock);
ret = 0;
}
}
clac();
return ret;
}
void set_hsm_notification_vector(uint32_t vector)
{
acrn_hsm_notification_vector = vector;
}
uint32_t get_hsm_notification_vector(void)
{
return acrn_hsm_notification_vector;
}
/**
* @brief General complete-work for MMIO emulation
*
* @param vcpu The virtual CPU that triggers the MMIO access
* @param io_req The I/O request holding the details of the MMIO access
*
* @pre io_req->io_type == ACRN_IOREQ_TYPE_MMIO
*
* @remark This function must be called when \p io_req is completed, after
* either a previous call to emulate_io() returning 0 or the corresponding HSM
* request transferring to the COMPLETE state.
*/
static void emulate_mmio_complete(struct acrn_vcpu *vcpu, const struct io_request *io_req)
{
const struct acrn_mmio_request *mmio_req = &io_req->reqs.mmio_request;
if (mmio_req->direction == ACRN_IOREQ_DIR_READ) {
/* Emulate instruction and update vcpu register set */
(void)emulate_instruction(vcpu);
}
}
static void complete_ioreq(struct acrn_vcpu *vcpu, struct io_request *io_req)
{
struct acrn_io_request_buffer *req_buf = NULL;
struct acrn_io_request *acrn_io_req;
req_buf = (struct acrn_io_request_buffer *)(vcpu->vm->sw.io_shared_page);
stac();
acrn_io_req = &req_buf->req_slot[vcpu->vcpu_id];
if (io_req != NULL) {
switch (vcpu->req.io_type) {
case ACRN_IOREQ_TYPE_PORTIO:
io_req->reqs.pio_request.value = acrn_io_req->reqs.pio_request.value;
break;
case ACRN_IOREQ_TYPE_MMIO:
io_req->reqs.mmio_request.value = acrn_io_req->reqs.mmio_request.value;
break;
default:
/*no actions are required for other cases.*/
break;
}
}
/*
* Only HV will check whether processed is ACRN_IOREQ_STATE_FREE on per-vCPU before inject a ioreq.
* Only HV will set processed to ACRN_IOREQ_STATE_FREE when ioreq is done.
*/
acrn_io_req->processed = ACRN_IOREQ_STATE_FREE;
clac();
}
/**
* @brief Complete-work of HSM requests for port I/O emulation
*
* @pre vcpu->req.io_type == ACRN_IOREQ_TYPE_PORTIO
*
* @remark This function must be called after the HSM request corresponding to
* \p vcpu being transferred to the COMPLETE state.
*/
static void dm_emulate_pio_complete(struct acrn_vcpu *vcpu)
{
struct io_request *io_req = &vcpu->req;
complete_ioreq(vcpu, io_req);
emulate_pio_complete(vcpu, io_req);
}
/**
* @brief Complete-work of HSM requests for MMIO emulation
*
* @param vcpu The virtual CPU that triggers the MMIO access
*
* @pre vcpu->req.io_type == ACRN_IOREQ_TYPE_MMIO
*
* @remark This function must be called after the HSM request corresponding to
* \p vcpu being transferred to the COMPLETE state.
*/
static void dm_emulate_mmio_complete(struct acrn_vcpu *vcpu)
{
struct io_request *io_req = &vcpu->req;
complete_ioreq(vcpu, io_req);
emulate_mmio_complete(vcpu, io_req);
}
/**
* @brief General complete-work for all kinds of HSM requests for I/O emulation
*
* @param vcpu The virtual CPU that triggers the MMIO access
*/
static void dm_emulate_io_complete(struct acrn_vcpu *vcpu)
{
if (get_io_req_state(vcpu->vm, vcpu->vcpu_id) == ACRN_IOREQ_STATE_COMPLETE) {
/*
* If vcpu is in Zombie state and will be destroyed soon. Just
* mark ioreq done and don't resume vcpu.
*/
if (vcpu->state == VCPU_ZOMBIE) {
complete_ioreq(vcpu, NULL);
} else {
switch (vcpu->req.io_type) {
case ACRN_IOREQ_TYPE_MMIO:
dm_emulate_mmio_complete(vcpu);
break;
case ACRN_IOREQ_TYPE_PORTIO:
case ACRN_IOREQ_TYPE_PCICFG:
/*
* ACRN_IOREQ_TYPE_PORTIO on 0xcf8 & 0xcfc may switch to
* ACRN_IOREQ_TYPE_PCICFG in some cases. It works to apply the post-work
* for ACRN_IOREQ_TYPE_PORTIO on ACRN_IOREQ_TYPE_PCICFG because the
* format of the first 28 bytes of ACRN_IOREQ_TYPE_PORTIO &
* ACRN_IOREQ_TYPE_PCICFG requests are exactly the same and post-work
* is mainly interested in the read value.
*/
dm_emulate_pio_complete(vcpu);
break;
default:
/*
* ACRN_IOREQ_TYPE_WP can only be triggered on writes which do
* not need post-work. Just mark the ioreq done.
*/
complete_ioreq(vcpu, NULL);
break;
}
}
}
}
/**
* @pre width < 8U
* @pre vcpu != NULL
* @pre vcpu->vm != NULL
*/
static bool pio_default_read(struct acrn_vcpu *vcpu,
__unused uint16_t addr, size_t width)
{
struct acrn_pio_request *pio_req = &vcpu->req.reqs.pio_request;
pio_req->value = (uint32_t)((1UL << (width * 8U)) - 1UL);
return true;
}
/**
* @pre width < 8U
* @pre vcpu != NULL
* @pre vcpu->vm != NULL
*/
static bool pio_default_write(__unused struct acrn_vcpu *vcpu, __unused uint16_t addr,
__unused size_t width, __unused uint32_t v)
{
return true; /* ignore write */
}
/**
* @pre (io_req->reqs.mmio.size == 1U) || (io_req->reqs.mmio.size == 2U) ||
* (io_req->reqs.mmio.size == 4U) || (io_req->reqs.mmio.size == 8U)
*/
static int32_t mmio_default_access_handler(struct io_request *io_req,
__unused void *handler_private_data)
{
struct acrn_mmio_request *mmio = &io_req->reqs.mmio_request;
if (mmio->direction == ACRN_IOREQ_DIR_READ) {
switch (mmio->size) {
case 1U:
mmio->value = MMIO_DEFAULT_VALUE_SIZE_1;
break;
case 2U:
mmio->value = MMIO_DEFAULT_VALUE_SIZE_2;
break;
case 4U:
mmio->value = MMIO_DEFAULT_VALUE_SIZE_4;
break;
case 8U:
mmio->value = MMIO_DEFAULT_VALUE_SIZE_8;
break;
default:
/* This case is unreachable, this is guaranteed by the design. */
break;
}
}
return 0;
}
/**
* Try handling the given request by any port I/O handler registered in the
* hypervisor.
*
* @pre io_req->io_type == ACRN_IOREQ_TYPE_PORTIO
*
* @retval 0 Successfully emulated by registered handlers.
* @retval -ENODEV No proper handler found.
* @retval -EIO The request spans multiple devices and cannot be emulated.
*/
static int32_t
hv_emulate_pio(struct acrn_vcpu *vcpu, struct io_request *io_req)
{
int32_t status = -ENODEV;
uint16_t port, size;
uint32_t idx;
struct acrn_vm *vm = vcpu->vm;
struct acrn_pio_request *pio_req = &io_req->reqs.pio_request;
struct vm_io_handler_desc *handler;
io_read_fn_t io_read = NULL;
io_write_fn_t io_write = NULL;
if (is_service_vm(vcpu->vm) || is_prelaunched_vm(vcpu->vm)) {
io_read = pio_default_read;
io_write = pio_default_write;
}
port = (uint16_t)pio_req->address;
size = (uint16_t)pio_req->size;
for (idx = 0U; idx < EMUL_PIO_IDX_MAX; idx++) {
handler = &(vm->emul_pio[idx]);
if ((port < handler->port_start) || (port >= handler->port_end)) {
continue;
}
if (handler->io_read != NULL) {
io_read = handler->io_read;
}
if (handler->io_write != NULL) {
io_write = handler->io_write;
}
break;
}
if ((pio_req->direction == ACRN_IOREQ_DIR_WRITE) && (io_write != NULL)) {
if (io_write(vcpu, port, size, pio_req->value)) {
status = 0;
}
} else if ((pio_req->direction == ACRN_IOREQ_DIR_READ) && (io_read != NULL)) {
if (io_read(vcpu, port, size)) {
status = 0;
}
} else {
/* do nothing */
}
pr_dbg("IO %s on port %04x, data %08x",
(pio_req->direction == ACRN_IOREQ_DIR_READ) ? "read" : "write", port, pio_req->value);
return status;
}
/**
* Use registered MMIO handlers on the given request if it falls in the range of
* any of them.
*
* @pre io_req->io_type == ACRN_IOREQ_TYPE_MMIO
*
* @retval 0 Successfully emulated by registered handlers.
* @retval -ENODEV No proper handler found.
* @retval -EIO The request spans multiple devices and cannot be emulated.
*/
static int32_t
hv_emulate_mmio(struct acrn_vcpu *vcpu, struct io_request *io_req)
{
int32_t status = -ENODEV;
bool hold_lock = true;
uint16_t idx;
uint64_t address, size, base, end;
struct acrn_mmio_request *mmio_req = &io_req->reqs.mmio_request;
struct mem_io_node *mmio_handler = NULL;
hv_mem_io_handler_t read_write = NULL;
void *handler_private_data = NULL;
if (is_service_vm(vcpu->vm) || is_prelaunched_vm(vcpu->vm)) {
read_write = mmio_default_access_handler;
}
address = mmio_req->address;
size = mmio_req->size;
spinlock_obtain(&vcpu->vm->emul_mmio_lock);
for (idx = 0U; idx <= vcpu->vm->nr_emul_mmio_regions; idx++) {
mmio_handler = &(vcpu->vm->emul_mmio[idx]);
if (mmio_handler->read_write != NULL) {
base = mmio_handler->range_start;
end = mmio_handler->range_end;
if (((address + size) <= base) || (address >= end)) {
continue;
} else {
if ((address >= base) && ((address + size) <= end)) {
hold_lock = mmio_handler->hold_lock;
read_write = mmio_handler->read_write;
handler_private_data = mmio_handler->handler_private_data;
} else {
pr_fatal("Err MMIO, address:0x%lx, size:%x", address, size);
status = -EIO;
}
break;
}
}
}
if ((status == -ENODEV) && (read_write != NULL)) {
/* This mmio_handler will never modify once register, so we don't
* need to hold the lock when handling the MMIO access.
*/
if (!hold_lock) {
spinlock_release(&vcpu->vm->emul_mmio_lock);
}
status = read_write(io_req, handler_private_data);
if (!hold_lock) {
spinlock_obtain(&vcpu->vm->emul_mmio_lock);
}
}
spinlock_release(&vcpu->vm->emul_mmio_lock);
return status;
}
/**
* @brief Emulate \p io_req for \p vcpu
*
* Handle an I/O request by either invoking a hypervisor-internal handler or
* deliver to HSM.
*
* @pre vcpu != NULL
* @pre vcpu->vm != NULL
* @pre vcpu->vm->vm_id < CONFIG_MAX_VM_NUM
*
* @param vcpu The virtual CPU that triggers the MMIO access
* @param io_req The I/O request holding the details of the MMIO access
*
* @retval 0 Successfully emulated by registered handlers.
* @retval ACRN_IOREQ_STATE_PENDING The I/O request is delivered to HSM.
* @retval -EIO The request spans multiple devices and cannot be emulated.
* @retval -EINVAL \p io_req has an invalid io_type.
* @retval <0 on other errors during emulation.
*/
int32_t
emulate_io(struct acrn_vcpu *vcpu, struct io_request *io_req)
{
int32_t status;
struct acrn_vm_config *vm_config;
struct asyncio_desc *aio_desc;
vm_config = get_vm_config(vcpu->vm->vm_id);
switch (io_req->io_type) {
case ACRN_IOREQ_TYPE_PORTIO:
status = hv_emulate_pio(vcpu, io_req);
if (status == 0) {
emulate_pio_complete(vcpu, io_req);
}
break;
case ACRN_IOREQ_TYPE_MMIO:
case ACRN_IOREQ_TYPE_WP:
status = hv_emulate_mmio(vcpu, io_req);
if (status == 0) {
emulate_mmio_complete(vcpu, io_req);
}
break;
default:
/* Unknown I/O request io_type */
status = -EINVAL;
break;
}
if ((status == -ENODEV) && (vm_config->load_order == POST_LAUNCHED_VM)) {
/*
* No handler from HV side, search from HSM in Service VM
*
* ACRN insert request to HSM and inject upcall.
*/
aio_desc = get_asyncio_desc(vcpu, io_req);
if (aio_desc) {
status = acrn_insert_asyncio(vcpu, aio_desc->asyncio_info.fd);
} else {
status = acrn_insert_request(vcpu, io_req);
if (status == 0) {
dm_emulate_io_complete(vcpu);
}
}
if (status != 0) {
/* here for both IO & MMIO, the direction, address,
* size definition is same
*/
struct acrn_pio_request *pio_req = &io_req->reqs.pio_request;
pr_fatal("%s Err: access dir %d, io_type %d, addr = 0x%lx, size=%lu", __func__,
pio_req->direction, io_req->io_type,
pio_req->address, pio_req->size);
}
}
return status;
}
/**
* @brief Register a port I/O handler
*
* @param vm The VM to which the port I/O handlers are registered
* @param pio_idx The emulated port io index
* @param range The emulated port io range
* @param io_read_fn_ptr The handler for emulating reads from the given range
* @param io_write_fn_ptr The handler for emulating writes to the given range
* @pre pio_idx < EMUL_PIO_IDX_MAX
*/
void register_pio_emulation_handler(struct acrn_vm *vm, uint32_t pio_idx,
const struct vm_io_range *range, io_read_fn_t io_read_fn_ptr, io_write_fn_t io_write_fn_ptr)
{
if (is_service_vm(vm)) {
deny_guest_pio_access(vm, range->base, range->len);
}
vm->emul_pio[pio_idx].port_start = range->base;
vm->emul_pio[pio_idx].port_end = range->base + range->len;
vm->emul_pio[pio_idx].io_read = io_read_fn_ptr;
vm->emul_pio[pio_idx].io_write = io_write_fn_ptr;
}
/**
* @brief Find match MMIO node
*
* This API find match MMIO node from \p vm.
*
* @param vm The VM to which the MMIO node is belong to.
*
* @return If there's a match mmio_node return it, otherwise return NULL;
*/
static inline struct mem_io_node *find_match_mmio_node(struct acrn_vm *vm,
uint64_t start, uint64_t end)
{
bool found = false;
uint16_t idx;
struct mem_io_node *mmio_node;
for (idx = 0U; idx < CONFIG_MAX_EMULATED_MMIO_REGIONS; idx++) {
mmio_node = &(vm->emul_mmio[idx]);
if ((mmio_node->range_start == start) && (mmio_node->range_end == end)) {
found = true;
break;
}
}
if (!found) {
pr_info("%s, vm[%d] no match mmio region [0x%lx, 0x%lx] is found",
__func__, vm->vm_id, start, end);
mmio_node = NULL;
}
return mmio_node;
}
/**
* @brief Find a free MMIO node
*
* This API find a free MMIO node from \p vm.
*
* @param vm The VM to which the MMIO node is belong to.
*
* @return If there's a free mmio_node return it, otherwise return NULL;
*/
static inline struct mem_io_node *find_free_mmio_node(struct acrn_vm *vm)
{
uint16_t idx;
struct mem_io_node *mmio_node = find_match_mmio_node(vm, 0UL, 0UL);
if (mmio_node != NULL) {
idx = (uint16_t)(uint64_t)(mmio_node - &(vm->emul_mmio[0U]));
if (vm->nr_emul_mmio_regions < idx) {
vm->nr_emul_mmio_regions = idx;
}
}
return mmio_node;
}
/**
* @brief Register a MMIO handler
*
* This API registers a MMIO handler to \p vm
*
* @param vm The VM to which the MMIO handler is registered
* @param read_write The handler for emulating accesses to the given range
* @param start The base address of the range \p read_write can emulate
* @param end The end of the range (exclusive) \p read_write can emulate
* @param handler_private_data Handler-specific data which will be passed to \p read_write when called
*/
void register_mmio_emulation_handler(struct acrn_vm *vm,
hv_mem_io_handler_t read_write, uint64_t start,
uint64_t end, void *handler_private_data, bool hold_lock)
{
struct mem_io_node *mmio_node;
/* Ensure both a read/write handler and range check function exist */
if ((read_write != NULL) && (end > start)) {
spinlock_obtain(&vm->emul_mmio_lock);
mmio_node = find_free_mmio_node(vm);
if (mmio_node != NULL) {
/* Fill in information for this node */
mmio_node->hold_lock = hold_lock;
mmio_node->read_write = read_write;
mmio_node->handler_private_data = handler_private_data;
mmio_node->range_start = start;
mmio_node->range_end = end;
}
spinlock_release(&vm->emul_mmio_lock);
}
}
/**
* @brief Unregister a MMIO handler
*
* This API unregisters a MMIO handler to \p vm
*
* @param vm The VM to which the MMIO handler is unregistered
* @param start The base address of the range which wants to unregister
* @param end The end of the range (exclusive) which wants to unregister
*/
void unregister_mmio_emulation_handler(struct acrn_vm *vm,
uint64_t start, uint64_t end)
{
struct mem_io_node *mmio_node;
spinlock_obtain(&vm->emul_mmio_lock);
mmio_node = find_match_mmio_node(vm, start, end);
if (mmio_node != NULL) {
(void)memset(mmio_node, 0U, sizeof(struct mem_io_node));
}
spinlock_release(&vm->emul_mmio_lock);
}
void deinit_emul_io(struct acrn_vm *vm)
{
(void)memset(vm->emul_mmio, 0U, sizeof(vm->emul_mmio));
(void)memset(vm->emul_pio, 0U, sizeof(vm->emul_pio));
}