mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-06-23 14:07:42 +00:00
HV: io: refactoring vmexit handler on I/O instruction
This patch refactors how I/O instructions are emulated, in order for a unify the I/O emulation path. The major control flow includes: 1. pio_instr_vmexit_handler (entry point for handling vmexit on I/O instruction): Extract port address, register size, direction and value (for write only), fill in an I/O request (of type io_request), invokes do_io to handle that and update the guest registers if the request has been successfully handled when do_io returns. 2. emulate_io: Handle the given I/O request. The request is handled or sent to VHM if it returns 0 (the actual status can be found in io_req->processed). On errors a negative error code is returned. 3. emulate_pio_by_handler: Look for the PIO handler for the given request and invoke that handler. Return 0 if a proper handler is found and invoked (the status of the emulation can be found in io_req->processed), -EIO when the request spans across devices, and -ENODEV when no handler is found. 4. emulate_pio_post: Update guest registers after the emulation is done. Currently this can happen either right after do_io() or after the vcpu is resumed. Status check on the I/O request and follow-up actions on failure will also go here. Note: Currently do_io can return 0 with io_req->processed being REQ_STATE_PENDING if the request is sent to VHM for further processing. In this case the current vcpu will be paused after handling this vm_exit, and dm_emulate_pio_post will be invoked to do the rest after this vcpu is resumed. When vcpus are scheduled back to exactly where they are scheduled out later, do_io should be responsible for the post_work and the processing of do_io results shall be mostly the same. v2 -> v3: * Rename: emulate_pio_by_handler -> hv_emulate_pio. * Properly mask the value passed to port I/O handler. v1 -> v2: * Rename: do_io -> emulate_io. * Rename io_instr_vmexit_handler -> pio_instr_vmexit_handler to reflect the fact that it handles port I/O only. Signed-off-by: Junjie Mao <junjie.mao@intel.com> Acked-by: Eddie Dong <eddie.dong@intel.com>
This commit is contained in:
parent
d4d8a128cf
commit
50e4bc1758
@ -6,140 +6,195 @@
|
|||||||
|
|
||||||
#include <hypervisor.h>
|
#include <hypervisor.h>
|
||||||
|
|
||||||
int dm_emulate_pio_post(struct vcpu *vcpu)
|
/**
|
||||||
|
* @pre io_req->type == REQ_PORTIO
|
||||||
|
*/
|
||||||
|
static int32_t
|
||||||
|
emulate_pio_post(struct vcpu *vcpu, struct io_request *io_req)
|
||||||
|
{
|
||||||
|
int32_t status;
|
||||||
|
struct pio_request *pio_req = &io_req->reqs.pio;
|
||||||
|
uint64_t mask = 0xFFFFFFFFUL >> (32UL - 8UL * pio_req->size);
|
||||||
|
|
||||||
|
if (io_req->processed == REQ_STATE_SUCCESS) {
|
||||||
|
if (pio_req->direction == REQUEST_READ) {
|
||||||
|
uint64_t value = (uint64_t)pio_req->value;
|
||||||
|
int32_t context_idx = vcpu->arch_vcpu.cur_context;
|
||||||
|
struct run_context *cur_context;
|
||||||
|
uint64_t *rax;
|
||||||
|
|
||||||
|
cur_context = &vcpu->arch_vcpu.contexts[context_idx];
|
||||||
|
rax = &cur_context->guest_cpu_regs.regs.rax;
|
||||||
|
|
||||||
|
*rax = ((*rax) & ~mask) | (value & mask);
|
||||||
|
}
|
||||||
|
status = 0;
|
||||||
|
} else {
|
||||||
|
status = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @pre vcpu->req.type == REQ_PORTIO
|
||||||
|
*/
|
||||||
|
int32_t dm_emulate_pio_post(struct vcpu *vcpu)
|
||||||
{
|
{
|
||||||
uint16_t cur = vcpu->vcpu_id;
|
uint16_t cur = vcpu->vcpu_id;
|
||||||
int cur_context = vcpu->arch_vcpu.cur_context;
|
|
||||||
union vhm_request_buffer *req_buf = NULL;
|
union vhm_request_buffer *req_buf = NULL;
|
||||||
struct io_request *io_req = &vcpu->req;
|
struct io_request *io_req = &vcpu->req;
|
||||||
struct pio_request *pio_req = &io_req->reqs.pio;
|
struct pio_request *pio_req = &io_req->reqs.pio;
|
||||||
uint64_t mask = 0xFFFFFFFFUL >> (32UL - 8UL * pio_req->size);
|
|
||||||
uint64_t *rax;
|
|
||||||
struct vhm_request *vhm_req;
|
struct vhm_request *vhm_req;
|
||||||
|
|
||||||
req_buf = (union vhm_request_buffer *)(vcpu->vm->sw.io_shared_page);
|
req_buf = (union vhm_request_buffer *)(vcpu->vm->sw.io_shared_page);
|
||||||
vhm_req = &req_buf->req_queue[cur];
|
vhm_req = &req_buf->req_queue[cur];
|
||||||
|
|
||||||
rax = &vcpu->arch_vcpu.contexts[cur_context].guest_cpu_regs.regs.rax;
|
|
||||||
io_req->processed = vhm_req->processed;
|
io_req->processed = vhm_req->processed;
|
||||||
pio_req->value = vhm_req->reqs.pio.value;
|
pio_req->value = vhm_req->reqs.pio.value;
|
||||||
|
|
||||||
/* VHM emulation data already copy to req, mark to free slot now */
|
/* VHM emulation data already copy to req, mark to free slot now */
|
||||||
vhm_req->valid = 0;
|
vhm_req->valid = 0;
|
||||||
|
|
||||||
if (io_req->processed != REQ_STATE_SUCCESS) {
|
return emulate_pio_post(vcpu, io_req);
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pio_req->direction == REQUEST_READ) {
|
|
||||||
uint64_t value = (uint64_t)pio_req->value;
|
|
||||||
*rax = ((*rax) & ~mask) | (value & mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
/**
|
||||||
dm_emulate_pio_pre(struct vcpu *vcpu, uint64_t exit_qual, uint64_t req_value)
|
* Try handling the given request by any port I/O handler registered in the
|
||||||
|
* hypervisor.
|
||||||
|
*
|
||||||
|
* @pre io_req->type == REQ_PORTIO
|
||||||
|
*
|
||||||
|
* @return 0 - Successfully emulated by registered handlers.
|
||||||
|
* @return -ENODEV - No proper handler found.
|
||||||
|
* @return -EIO - The request spans multiple devices and cannot be emulated.
|
||||||
|
*/
|
||||||
|
int32_t
|
||||||
|
hv_emulate_pio(struct vcpu *vcpu, struct io_request *io_req)
|
||||||
{
|
{
|
||||||
struct pio_request *pio_req = &vcpu->req.reqs.pio;
|
int32_t status = -ENODEV;
|
||||||
|
|
||||||
pio_req->value = req_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int io_instr_vmexit_handler(struct vcpu *vcpu)
|
|
||||||
{
|
|
||||||
uint64_t exit_qual;
|
|
||||||
uint64_t mask;
|
|
||||||
uint16_t port, size;
|
uint16_t port, size;
|
||||||
struct vm_io_handler *handler;
|
uint32_t mask;
|
||||||
struct vm *vm = vcpu->vm;
|
struct vm *vm = vcpu->vm;
|
||||||
struct io_request *io_req = &vcpu->req;
|
|
||||||
struct pio_request *pio_req = &io_req->reqs.pio;
|
struct pio_request *pio_req = &io_req->reqs.pio;
|
||||||
int cur_context_idx = vcpu->arch_vcpu.cur_context;
|
struct vm_io_handler *handler;
|
||||||
struct run_context *cur_context;
|
|
||||||
int status = -EINVAL;
|
|
||||||
|
|
||||||
io_req->type = REQ_PORTIO;
|
|
||||||
io_req->processed = REQ_STATE_PENDING;
|
|
||||||
|
|
||||||
cur_context = &vcpu->arch_vcpu.contexts[cur_context_idx];
|
|
||||||
exit_qual = vcpu->arch_vcpu.exit_qualification;
|
|
||||||
|
|
||||||
pio_req->size = VM_EXIT_IO_INSTRUCTION_SIZE(exit_qual) + 1UL;
|
|
||||||
pio_req->address = VM_EXIT_IO_INSTRUCTION_PORT_NUMBER(exit_qual);
|
|
||||||
if (VM_EXIT_IO_INSTRUCTION_ACCESS_DIRECTION(exit_qual) == 0UL) {
|
|
||||||
pio_req->direction = REQUEST_WRITE;
|
|
||||||
} else {
|
|
||||||
pio_req->direction = REQUEST_READ;
|
|
||||||
}
|
|
||||||
|
|
||||||
size = (uint16_t)pio_req->size;
|
|
||||||
port = (uint16_t)pio_req->address;
|
port = (uint16_t)pio_req->address;
|
||||||
mask = 0xffffffffUL >> (32U - 8U * size);
|
size = (uint16_t)pio_req->size;
|
||||||
|
mask = 0xFFFFFFFFU >> (32U - 8U * size);
|
||||||
|
|
||||||
TRACE_4I(TRACE_VMEXIT_IO_INSTRUCTION,
|
|
||||||
(uint32_t)port,
|
|
||||||
(uint32_t)pio_req->direction,
|
|
||||||
(uint32_t)size,
|
|
||||||
(uint32_t)cur_context_idx);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Post-conditions of the loop:
|
|
||||||
*
|
|
||||||
* status == 0 : The access has been handled properly.
|
|
||||||
* status == -EIO : The access spans multiple devices and cannot
|
|
||||||
* be handled.
|
|
||||||
* status == -EINVAL : No valid handler found for this access.
|
|
||||||
*/
|
|
||||||
for (handler = vm->arch_vm.io_handler;
|
for (handler = vm->arch_vm.io_handler;
|
||||||
handler; handler = handler->next) {
|
handler != NULL; handler = handler->next) {
|
||||||
|
uint16_t base = handler->desc.addr;
|
||||||
|
uint16_t end = base + (uint16_t)handler->desc.len;
|
||||||
|
|
||||||
if ((port >= (handler->desc.addr + handler->desc.len)) ||
|
if ((port >= end) || (port + size <= base)) {
|
||||||
(port + size <= handler->desc.addr)) {
|
|
||||||
continue;
|
continue;
|
||||||
} else if (!((port >= handler->desc.addr) && ((port + size)
|
} else if (!((port >= base) && ((port + size) <= end))) {
|
||||||
<= (handler->desc.addr + handler->desc.len)))) {
|
|
||||||
pr_fatal("Err:IO, port 0x%04x, size=%hu spans devices",
|
pr_fatal("Err:IO, port 0x%04x, size=%hu spans devices",
|
||||||
port, size);
|
port, size);
|
||||||
status = -EIO;
|
status = -EIO;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
struct cpu_gp_regs *regs =
|
|
||||||
&cur_context->guest_cpu_regs.regs;
|
|
||||||
|
|
||||||
if (pio_req->direction == REQUEST_WRITE) {
|
if (pio_req->direction == REQUEST_WRITE) {
|
||||||
handler->desc.io_write(handler, vm, port, size,
|
handler->desc.io_write(handler, vm, port, size,
|
||||||
regs->rax);
|
pio_req->value & mask);
|
||||||
|
|
||||||
pr_dbg("IO write on port %04x, data %08x", port,
|
pr_dbg("IO write on port %04x, data %08x", port,
|
||||||
regs->rax & mask);
|
pio_req->value & mask);
|
||||||
} else {
|
} else {
|
||||||
uint32_t data = handler->desc.io_read(handler,
|
pio_req->value = handler->desc.io_read(handler,
|
||||||
vm, port, size);
|
vm, port, size);
|
||||||
|
|
||||||
regs->rax &= ~mask;
|
|
||||||
regs->rax |= data & mask;
|
|
||||||
|
|
||||||
pr_dbg("IO read on port %04x, data %08x",
|
pr_dbg("IO read on port %04x, data %08x",
|
||||||
port, data);
|
port, pio_req->value);
|
||||||
}
|
}
|
||||||
|
/* TODO: failures in the handlers should be reflected
|
||||||
|
* here. */
|
||||||
|
io_req->processed = REQ_STATE_SUCCESS;
|
||||||
status = 0;
|
status = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Go for VHM */
|
return status;
|
||||||
if (status == -EINVAL) {
|
}
|
||||||
uint64_t rax = cur_context->guest_cpu_regs.regs.rax;
|
|
||||||
|
|
||||||
dm_emulate_pio_pre(vcpu, exit_qual, rax);
|
/**
|
||||||
|
* Handle an I/O request by either invoking a hypervisor-internal handler or
|
||||||
|
* deliver to VHM.
|
||||||
|
*
|
||||||
|
* @pre io_req->type == REQ_PORTIO
|
||||||
|
*
|
||||||
|
* @return 0 - Successfully emulated by registered handlers.
|
||||||
|
* @return -EIO - The request spans multiple devices and cannot be emulated.
|
||||||
|
* @return Negative on other errors during emulation.
|
||||||
|
*/
|
||||||
|
int32_t
|
||||||
|
emulate_io(struct vcpu *vcpu, struct io_request *io_req)
|
||||||
|
{
|
||||||
|
int32_t status;
|
||||||
|
|
||||||
|
status = hv_emulate_pio(vcpu, io_req);
|
||||||
|
|
||||||
|
if (status == -ENODEV) {
|
||||||
|
/*
|
||||||
|
* No handler from HV side, search from VHM in Dom0
|
||||||
|
*
|
||||||
|
* ACRN insert request to VHM and inject upcall.
|
||||||
|
*/
|
||||||
status = acrn_insert_request_wait(vcpu, io_req);
|
status = acrn_insert_request_wait(vcpu, io_req);
|
||||||
|
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
pr_fatal("Err:IO %s access to port 0x%04x, size=%u",
|
struct pio_request *pio_req = &io_req->reqs.pio;
|
||||||
|
pr_fatal("Err:IO %s access to port 0x%04lx, size=%lu",
|
||||||
(pio_req->direction != REQUEST_READ) ? "read" : "write",
|
(pio_req->direction != REQUEST_READ) ? "read" : "write",
|
||||||
port, size);
|
pio_req->address, pio_req->size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t pio_instr_vmexit_handler(struct vcpu *vcpu)
|
||||||
|
{
|
||||||
|
int32_t status;
|
||||||
|
uint64_t exit_qual;
|
||||||
|
int32_t cur_context_idx = vcpu->arch_vcpu.cur_context;
|
||||||
|
struct run_context *cur_context;
|
||||||
|
struct cpu_gp_regs *regs;
|
||||||
|
struct io_request *io_req = &vcpu->req;
|
||||||
|
struct pio_request *pio_req = &io_req->reqs.pio;
|
||||||
|
|
||||||
|
exit_qual = vcpu->arch_vcpu.exit_qualification;
|
||||||
|
cur_context = &vcpu->arch_vcpu.contexts[cur_context_idx];
|
||||||
|
regs = &cur_context->guest_cpu_regs.regs;
|
||||||
|
|
||||||
|
io_req->type = REQ_PORTIO;
|
||||||
|
io_req->processed = REQ_STATE_PENDING;
|
||||||
|
pio_req->size = VM_EXIT_IO_INSTRUCTION_SIZE(exit_qual) + 1UL;
|
||||||
|
pio_req->address = VM_EXIT_IO_INSTRUCTION_PORT_NUMBER(exit_qual);
|
||||||
|
if (VM_EXIT_IO_INSTRUCTION_ACCESS_DIRECTION(exit_qual) == 0UL) {
|
||||||
|
pio_req->direction = REQUEST_WRITE;
|
||||||
|
pio_req->value = (uint32_t)regs->rax;
|
||||||
|
} else {
|
||||||
|
pio_req->direction = REQUEST_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE_4I(TRACE_VMEXIT_IO_INSTRUCTION,
|
||||||
|
(uint32_t)pio_req->address,
|
||||||
|
(uint32_t)pio_req->direction,
|
||||||
|
(uint32_t)pio_req->size,
|
||||||
|
(uint32_t)cur_context_idx);
|
||||||
|
|
||||||
|
status = emulate_io(vcpu, io_req);
|
||||||
|
|
||||||
|
/* io_req is hypervisor-private. For requests sent to VHM,
|
||||||
|
* io_req->processed will be PENDING till dm_emulate_pio_post() is
|
||||||
|
* called on vcpu resume. */
|
||||||
|
if (status == 0) {
|
||||||
|
if (io_req->processed != REQ_STATE_PENDING) {
|
||||||
|
status = emulate_pio_post(vcpu, io_req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ static const struct vm_exit_dispatch dispatch_table[NR_VMX_EXIT_REASONS] = {
|
|||||||
[VMX_EXIT_REASON_DR_ACCESS] = {
|
[VMX_EXIT_REASON_DR_ACCESS] = {
|
||||||
.handler = unhandled_vmexit_handler},
|
.handler = unhandled_vmexit_handler},
|
||||||
[VMX_EXIT_REASON_IO_INSTRUCTION] = {
|
[VMX_EXIT_REASON_IO_INSTRUCTION] = {
|
||||||
.handler = io_instr_vmexit_handler,
|
.handler = pio_instr_vmexit_handler,
|
||||||
.need_exit_qualification = 1},
|
.need_exit_qualification = 1},
|
||||||
[VMX_EXIT_REASON_RDMSR] = {
|
[VMX_EXIT_REASON_RDMSR] = {
|
||||||
.handler = rdmsr_vmexit_handler},
|
.handler = rdmsr_vmexit_handler},
|
||||||
|
@ -108,7 +108,7 @@ struct mem_io_node {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* External Interfaces */
|
/* External Interfaces */
|
||||||
int io_instr_vmexit_handler(struct vcpu *vcpu);
|
int32_t pio_instr_vmexit_handler(struct vcpu *vcpu);
|
||||||
void setup_io_bitmap(struct vm *vm);
|
void setup_io_bitmap(struct vm *vm);
|
||||||
void free_io_emulation_resource(struct vm *vm);
|
void free_io_emulation_resource(struct vm *vm);
|
||||||
void allow_guest_io_access(struct vm *vm, uint32_t address_arg, uint32_t nbytes);
|
void allow_guest_io_access(struct vm *vm, uint32_t address_arg, uint32_t nbytes);
|
||||||
|
Loading…
Reference in New Issue
Block a user