blob: 4bb3d6cf3b78c8821dc1135bcb32157cc6ca46aa [file] [log] [blame]
/*
* Copyright 2019, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*/
#include <stdio.h>
#include <stdlib.h>
#include <sel4utils/util.h>
#include <sel4vmmplatsupport/ioports.h>
static int io_port_compare_by_range(const void *pkey, const void *pelem)
{
unsigned int key = (unsigned int)(uintptr_t)pkey;
const ioport_entry_t *entry = (const ioport_entry_t *)(*(const ioport_entry_t **)pelem);
const ioport_range_t *elem = &entry->range;
if (key < elem->start) {
return -1;
}
if (key > elem->end) {
return 1;
}
return 0;
}
static int io_port_compare_by_start(const void *a, const void *b)
{
const ioport_entry_t *a_entry = (const ioport_entry_t *)(*(const ioport_entry_t **)a);
const ioport_range_t *a_range = &a_entry->range;
const ioport_entry_t *b_entry = (const ioport_entry_t *)(*(const ioport_entry_t **)b);
const ioport_range_t *b_range = &b_entry->range;
return a_range->start - b_range->start;
}
static ioport_entry_t **search_port(vmm_io_port_list_t *io_port, unsigned int port_no)
{
return (ioport_entry_t **)bsearch((void *)(uintptr_t)port_no, io_port->ioports, io_port->num_ioports,
sizeof(ioport_entry_t *), io_port_compare_by_range);
}
/* Debug helper function for port no. */
static const char *vmm_debug_io_portno_desc(vmm_io_port_list_t *io_port, int port_no)
{
ioport_entry_t **res_port = search_port(io_port, port_no);
return res_port ? (*res_port)->interface.desc : "Unknown IO Port";
}
/* IO execution handler. */
int emulate_io_handler(vmm_io_port_list_t *io_port, unsigned int port_no, bool is_in, size_t size, unsigned int *data)
{
unsigned int value;
if (io_port == NULL) {
ZF_LOGE("Unable to emulate port - io port list is uninitalised");
return -1;
}
ZF_LOGI("exit io request: in %d port no 0x%x (%s) size %d\n",
is_in, port_no, vmm_debug_io_portno_desc(io_port, port_no), size);
ioport_entry_t **res_port = search_port(io_port, port_no);
if (!res_port) {
static int last_port = -1;
if (last_port != port_no) {
ZF_LOGW("exit io request: WARNING - ignoring unsupported ioport 0x%x (%s)\n", port_no,
vmm_debug_io_portno_desc(io_port, port_no));
last_port = port_no;
}
return 1;
}
ioport_entry_t *port = *res_port;
int ret = 0;
if (is_in) {
ret = port->interface.port_in(port->interface.cookie, port_no, size, data);
} else {
ret = port->interface.port_out(port->interface.cookie, port_no, size, *data);
}
if (ret) {
ZF_LOGE("exit io request: handler returned error.");
ZF_LOGE("exit io ERROR: string %d in %d rep %d port no 0x%x (%s) size %d", 0,
is_in, 0, port_no, vmm_debug_io_portno_desc(io_port, port_no), size);
return -1;
}
return 0;
}
static int add_io_port_range(vmm_io_port_list_t *io_list, ioport_entry_t *port)
{
if (io_list == NULL) {
ZF_LOGE("Unable to add port - io port list is uninitalised");
return -1;
}
/* ensure this range does not overlap */
for (int i = 0; i < io_list->num_ioports; i++) {
if (io_list->ioports[i]->range.end >= port->range.start && io_list->ioports[i]->range.start <= port->range.end) {
ZF_LOGE("Requested ioport range 0x%x-0x%x for %s overlaps with existing range 0x%x-0x%x for %s",
port->range.start, port->range.end, port->interface.desc ? port->interface.desc : "Unknown IO Port",
io_list->ioports[i]->range.start, io_list->ioports[i]->range.end,
io_list->ioports[i]->interface.desc ? io_list->ioports[i]->interface.desc : "Unknown IO Port");
return -1;
}
}
/* grow the array */
io_list->ioports = realloc(io_list->ioports, sizeof(ioport_entry_t *) * (io_list->num_ioports + 1));
assert(io_list->ioports);
/* add the new entry */
io_list->ioports[io_list->num_ioports] = port;
io_list->num_ioports++;
/* sort */
qsort(io_list->ioports, io_list->num_ioports, sizeof(ioport_entry_t *), io_port_compare_by_start);
return 0;
}
static int alloc_free_io_port_range(vmm_io_port_list_t *io_list, ioport_range_t *io_range)
{
uint16_t free_port_addr = io_list->alloc_addr;
if (free_port_addr + io_range->size < free_port_addr) {
/* Possible overflow */
return -1;
}
io_list->alloc_addr += io_range->size;
io_range->start = free_port_addr;
io_range->end = free_port_addr + io_range->size - 1;
return 0;
}
static void free_io_port_range(vmm_io_port_list_t *io_list, ioport_range_t *io_range)
{
io_list->alloc_addr -= io_range->size;
}
/* Add an io port range for emulation */
ioport_entry_t *vmm_io_port_add_handler(vmm_io_port_list_t *io_list, ioport_range_t io_range,
ioport_interface_t io_interface, ioport_type_t port_type)
{
int err;
if (port_type == IOPORT_FREE) {
err = alloc_free_io_port_range(io_list, &io_range);
if (err) {
return NULL;
}
}
ioport_entry_t *entry = calloc(1, sizeof(ioport_entry_t));
if (!entry) {
free_io_port_range(io_list, &io_range);
return NULL;
}
*entry = (ioport_entry_t) {
io_range, io_interface
};
err = add_io_port_range(io_list, entry);
if (err) {
free_io_port_range(io_list, &io_range);
return NULL;
}
return entry;
}
int vmm_io_port_init(vmm_io_port_list_t **io_list, uint16_t ioport_alloc_addr)
{
vmm_io_port_list_t *init_iolist = (vmm_io_port_list_t *)calloc(1, sizeof(vmm_io_port_list_t));
if (init_iolist == NULL) {
ZF_LOGE("Failed to calloc memory for io port list");
return -1;
}
init_iolist->num_ioports = 0;
init_iolist->ioports = NULL;
init_iolist->alloc_addr = ioport_alloc_addr;
*io_list = init_iolist;
return 0;
}