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