1/* 2 * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) 3 * 4 * SPDX-License-Identifier: GPL-2.0-only 5 */ 6 7/*vm exits related with io instructions*/ 8 9#include <stdio.h> 10#include <stdlib.h> 11 12#include <sel4/sel4.h> 13#include <sel4utils/util.h> 14#include <simple/simple.h> 15 16#include <sel4vm/guest_vm.h> 17#include <sel4vm/arch/ioports.h> 18#include <sel4vm/arch/guest_x86_context.h> 19 20#include "vm.h" 21#include "guest_state.h" 22 23static int io_port_compare_by_range(const void *pkey, const void *pelem) 24{ 25 unsigned int key = (unsigned int)(uintptr_t)pkey; 26 const vm_ioport_entry_t *entry = (const vm_ioport_entry_t *)pelem; 27 const vm_ioport_range_t *elem = &entry->range; 28 if (key < elem->start) { 29 return -1; 30 } 31 if (key > elem->end) { 32 return 1; 33 } 34 return 0; 35} 36 37static int io_port_compare_by_start(const void *a, const void *b) 38{ 39 const vm_ioport_entry_t *a_entry = (const vm_ioport_entry_t *)a; 40 const vm_ioport_range_t *a_range = &a_entry->range; 41 const vm_ioport_entry_t *b_entry = (const vm_ioport_entry_t *)b; 42 const vm_ioport_range_t *b_range = &b_entry->range; 43 return a_range->start - b_range->start; 44} 45 46static vm_ioport_entry_t *search_port(vm_io_port_list_t *ioports, unsigned int port_no) 47{ 48 return (vm_ioport_entry_t *)bsearch((void *)(uintptr_t)port_no, ioports->ioports, ioports->num_ioports, 49 sizeof(vm_ioport_entry_t), io_port_compare_by_range); 50} 51 52static void set_io_in_unhandled(vm_vcpu_t *vcpu, unsigned int size) 53{ 54 uint32_t eax; 55 if (size < 4) { 56 vm_get_thread_context_reg(vcpu, VCPU_CONTEXT_EAX, &eax); 57 eax |= MASK(size * 8); 58 } else { 59 eax = -1; 60 } 61 vm_set_thread_context_reg(vcpu, VCPU_CONTEXT_EAX, eax); 62} 63 64static void set_io_in_value(vm_vcpu_t *vcpu, unsigned int value, unsigned int size) 65{ 66 uint32_t eax; 67 if (size < 4) { 68 vm_get_thread_context_reg(vcpu, VCPU_CONTEXT_EAX, &eax); 69 eax &= ~MASK(size * 8); 70 eax |= value; 71 } else { 72 eax = value; 73 } 74 vm_set_thread_context_reg(vcpu, VCPU_CONTEXT_EAX, eax); 75} 76 77static int add_io_port_range(vm_io_port_list_t *ioport_list, vm_ioport_entry_t port) 78{ 79 /* ensure this range does not overlap */ 80 for (int i = 0; i < ioport_list->num_ioports; i++) { 81 if (ioport_list->ioports[i].range.end > port.range.start && ioport_list->ioports[i].range.start < port.range.end) { 82 ZF_LOGE("Requested ioport range 0x%x-0x%x for %s overlaps with existing range 0x%x-0x%x for %s", 83 port.range.start, port.range.end, port.interface.desc ? port.interface.desc : "Unknown IO Port", 84 ioport_list->ioports[i].range.start, ioport_list->ioports[i].range.end, 85 ioport_list->ioports[i].interface.desc ? ioport_list->ioports[i].interface.desc : "Unknown IO Port"); 86 return -1; 87 } 88 } 89 /* grow the array */ 90 ioport_list->ioports = realloc(ioport_list->ioports, sizeof(vm_ioport_entry_t) * (ioport_list->num_ioports + 1)); 91 assert(ioport_list->ioports); 92 /* add the new entry */ 93 ioport_list->ioports[ioport_list->num_ioports] = port; 94 ioport_list->num_ioports++; 95 /* sort */ 96 qsort(ioport_list->ioports, ioport_list->num_ioports, sizeof(vm_ioport_entry_t), io_port_compare_by_start); 97 return 0; 98} 99 100int vm_enable_passthrough_ioport(vm_vcpu_t *vcpu, uint16_t port_start, uint16_t port_end) 101{ 102 cspacepath_t path; 103 int error; 104 ZF_LOGD("Enabling IO port 0x%x - 0x%x for passthrough", port_start, port_end); 105 error = vka_cspace_alloc_path(vcpu->vm->vka, &path); 106 if (error) { 107 ZF_LOGE("Failed to allocate slot"); 108 return error; 109 } 110 error = simple_get_IOPort_cap(vcpu->vm->simple, port_start, port_end, path.root, path.capPtr, path.capDepth); 111 if (error) { 112 ZF_LOGE("Failed to get io port from simple for range 0x%x - 0x%x", port_start, port_end); 113 return error; 114 } 115 error = seL4_X86_VCPU_EnableIOPort(vcpu->vcpu.cptr, path.capPtr, port_start, port_end); 116 if (error != seL4_NoError) { 117 ZF_LOGE("Failed to enable io port"); 118 return error; 119 } 120 return 0; 121} 122 123/* IO instruction execution handler. */ 124int vm_io_instruction_handler(vm_vcpu_t *vcpu) 125{ 126 127 unsigned int exit_qualification = vm_guest_exit_get_qualification(vcpu->vcpu_arch.guest_state); 128 unsigned int string, rep; 129 int ret; 130 unsigned int port_no; 131 unsigned int size; 132 unsigned int value; 133 int is_in; 134 ioport_fault_result_t res; 135 136 string = (exit_qualification & 16) != 0; 137 is_in = (exit_qualification & 8) != 0; 138 port_no = exit_qualification >> 16; 139 size = (exit_qualification & 7) + 1; 140 rep = (exit_qualification & 0x20) >> 5; 141 142 /*FIXME: does not support string and rep instructions*/ 143 if (string || rep) { 144 ZF_LOGE("FIXME: IO exit does not support string and rep instructions"); 145 return VM_EXIT_HANDLE_ERROR; 146 } 147 148 if (!is_in) { 149 ret = vm_get_thread_context_reg(vcpu, VCPU_CONTEXT_EAX, &value); 150 if (ret) { 151 return VM_EXIT_HANDLE_ERROR; 152 } 153 if (size < 4) { 154 value &= MASK(size * 8); 155 } 156 } 157 158 /* Search internal ioport list */ 159 vm_ioport_entry_t *port = search_port(&vcpu->vm->arch.ioport_list, port_no); 160 if (port) { 161 if (is_in) { 162 res = port->interface.port_in(vcpu, port->interface.cookie, port_no, size, &value); 163 } else { 164 res = port->interface.port_out(vcpu, port->interface.cookie, port_no, size, value); 165 } 166 } else if (vcpu->vm->arch.unhandled_ioport_callback) { 167 res = vcpu->vm->arch.unhandled_ioport_callback(vcpu, port_no, is_in, &value, size, 168 vcpu->vm->arch.unhandled_ioport_callback_cookie); 169 } else { 170 /* No means of handling ioport instruction */ 171 if (port_no != -1) { 172 ZF_LOGW("ignoring unsupported ioport 0x%x", port_no); 173 } 174 if (is_in) { 175 set_io_in_unhandled(vcpu, size); 176 } 177 vm_guest_exit_next_instruction(vcpu->vcpu_arch.guest_state, vcpu->vcpu.cptr); 178 return VM_EXIT_HANDLED; 179 } 180 181 if (is_in) { 182 if (res == IO_FAULT_UNHANDLED) { 183 set_io_in_unhandled(vcpu, size); 184 } else { 185 set_io_in_value(vcpu, value, size); 186 } 187 } 188 189 if (res == IO_FAULT_ERROR) { 190 ZF_LOGE("VM Exit IO Error: string %d in %d rep %d port no 0x%x size %d", 0, 191 is_in, 0, port_no, size); 192 return VM_EXIT_HANDLE_ERROR; 193 } 194 195 vm_guest_exit_next_instruction(vcpu->vcpu_arch.guest_state, vcpu->vcpu.cptr); 196 197 return VM_EXIT_HANDLED; 198} 199 200int vm_register_unhandled_ioport_callback(vm_t *vm, unhandled_ioport_callback_fn ioport_callback, 201 void *cookie) 202{ 203 if (!vm) { 204 ZF_LOGE("Failed to register ioport callback: Invalid VM handle"); 205 return -1; 206 } 207 208 if (!ioport_callback) { 209 ZF_LOGE("Failed to register ioport callback: Invalid callback"); 210 return -1; 211 } 212 vm->arch.unhandled_ioport_callback = ioport_callback; 213 vm->arch.unhandled_ioport_callback_cookie = cookie; 214 return 0; 215} 216 217int vm_io_port_add_handler(vm_t *vm, vm_ioport_range_t io_range, vm_ioport_interface_t io_interface) 218{ 219 return add_io_port_range(&vm->arch.ioport_list, (vm_ioport_entry_t) { 220 io_range, io_interface 221 }); 222} 223