diff --git a/src/virtio/console.c b/src/virtio/console.c new file mode 100644 index 00000000..805a7036 --- /dev/null +++ b/src/virtio/console.c @@ -0,0 +1,275 @@ +#include "virtio/config.h" +#include "virtio/mmio.h" +#include "virtio/console.h" +#include "util/util.h" +#include "virq.h" + +/* Uncomment this to enable debug logging */ +// #define DEBUG_CONSOLE + +#if defined(DEBUG_CONSOLE) +#define LOG_CONSOLE(...) do{ printf("VIRTIO(CONSOLE): "); printf(__VA_ARGS__); }while(0) +#else +#define LOG_CONSOLE(...) do{}while(0) +#endif + +#define LOG_CONSOLE_ERR(...) do{ printf("VIRTIO(CONSOLE)|ERROR: "); printf(__VA_ARGS__); }while(0) + +// @ivanv: put in util or remove +#define BIT_LOW(n) (1ul<<(n)) +#define BIT_HIGH(n) (1ul<<(n - 32 )) + +static void virtio_console_features_print(uint32_t features) { + /* Dump the features given in a human-readable format */ + LOG_CONSOLE("Dumping features (0x%lx):\n", features); + LOG_CONSOLE("feature VIRTIO_CONSOLE_F_SIZE set to %s\n", + BIT_LOW(VIRTIO_CONSOLE_F_SIZE) & features ? "true" : "false"); + LOG_CONSOLE("feature VIRTIO_CONSOLE_F_MULTIPORT set to %s\n", + BIT_LOW(VIRTIO_CONSOLE_F_MULTIPORT) & features ? "true" : "false"); + LOG_CONSOLE("feature VIRTIO_CONSOLE_F_EMERG_WRITE set to %s\n", + BIT_LOW(VIRTIO_CONSOLE_F_EMERG_WRITE) & features ? "true" : "false"); +} + +static void virtio_console_reset(struct virtio_device *dev) { + LOG_CONSOLE("operation: reset\n"); + LOG_CONSOLE_ERR("virtio_console_reset is not implemented!\n"); + + // @ivanv reset vqs? +} + +static int virtio_console_get_device_features(struct virtio_device *dev, uint32_t *features) { + LOG_CONSOLE("operation: get device features\n"); + + switch (dev->data.DeviceFeaturesSel) { + case 0: + *features = 0; + break; + case 1: + *features = BIT_HIGH(VIRTIO_F_VERSION_1); + break; + default: + // @ivanv: audit + LOG_CONSOLE_ERR("driver sets DeviceFeaturesSel to 0x%x, which doesn't make sense\n", dev->data.DeviceFeaturesSel); + return 0; + } + + return 1; +} + +static int virtio_console_set_driver_features(struct virtio_device *dev, uint32_t features) { + LOG_CONSOLE("operation: set driver features\n"); + virtio_console_features_print(features); + + int success = 1; + + switch (dev->data.DriverFeaturesSel) { + // feature bits 0 to 31 + case 0: + // The device initialisation protocol says the driver should read device feature bits, + // and write the subset of feature bits understood by the OS and driver to the device. + // Currently we only have one feature to check. + // success = (features == BIT_LOW(VIRTIO_NET_F_MAC)); + break; + // features bits 32 to 63 + case 1: + success = (features == BIT_HIGH(VIRTIO_F_VERSION_1)); + break; + default: + LOG_CONSOLE_ERR("driver sets DriverFeaturesSel to 0x%x, which doesn't make sense\n", dev->data.DriverFeaturesSel); + return false; + } + + if (success) { + dev->data.features_happy = 1; + LOG_CONSOLE("device is feature happy\n"); + } + + return success; +} + +static int virtio_console_get_device_config(struct virtio_device *dev, uint32_t offset, uint32_t *config) { + LOG_CONSOLE("operation: get device config\n"); + return -1; +} + +static int virtio_console_set_device_config(struct virtio_device *dev, uint32_t offset, uint32_t config) { + LOG_CONSOLE("operation: set device config\n"); + return -1; +} + +/* The guest has notified us that it has placed something in the transmit queue. */ +static int virtio_console_handle_tx(struct virtio_device *dev) { + LOG_CONSOLE("operation: handle transmit\n"); + + // @ivanv: we need to check the pre-conditions before doing anything. e.g check + // TX_QUEUE is ready? + + assert(dev->num_vqs > TX_QUEUE); + struct virtio_queue_handler *tx_queue = &dev->vqs[TX_QUEUE]; + struct virtq *virtq = &tx_queue->virtq; + uint16_t guest_idx = virtq->avail->idx; + size_t idx = tx_queue->last_idx; + + while (idx != guest_idx) { + LOG_CONSOLE("processing available buffers from index [0x%lx..0x%lx)\n", idx, guest_idx); + uint16_t desc_head = virtq->avail->ring[idx % virtq->num]; + + /* Continue traversing the chained buffers */ + struct virtq_desc desc; + uint16_t desc_idx = desc_head; + do { + desc = virtq->desc[desc_idx]; + LOG_CONSOLE("processing descriptor (0x%lx) with buffer [0x%lx..0x%lx)\n", desc_idx, desc.addr, desc.addr + desc.len); + // @ivanv: to the debug logging, we should actually print out teh buffer contents + + /* Now that we have a buffer, we can transfer the data to the sDDF multiplexor */ + /* We first need a free buffer from the TX ring */ + uintptr_t sddf_buffer = 0; + unsigned int sddf_buffer_len = 0; + void *sddf_cookie = NULL; + LOG_CONSOLE("tx ring free size: 0x%lx, tx ring used size: 0x%lx\n", ring_size(dev->sddf_tx_ring->free_ring), ring_size(dev->sddf_tx_ring->used_ring)); + assert(!ring_empty(dev->sddf_tx_ring->free_ring)); + int ret = dequeue_free(dev->sddf_tx_ring, &sddf_buffer, &sddf_buffer_len, &sddf_cookie); + assert(!ret); + if (ret != 0) { + LOG_CONSOLE_ERR("could not dequeue from the TX free ring\n"); + // @ivanv: todo, handle this properly + } + + // @ivanv: handle this in release mode + if (desc.len > sddf_buffer_len) { + LOG_CONSOLE_ERR("%s expected sddf_buffer_len (0x%lx) <= desc.len (0x%lx)\n", microkit_name, sddf_buffer_len, desc.len); + } + assert(desc.len <= sddf_buffer_len); + + /* Copy the data over, these buffers are in the guests's RAM and hence inaccessible + * by the multiplexor. */ + memcpy((char *) sddf_buffer, (char *) desc.addr, desc.len); + + bool is_empty = ring_empty(dev->sddf_tx_ring->used_ring); + /* Now we can enqueue our buffer into the used TX ring */ + ret = enqueue_used(dev->sddf_tx_ring, sddf_buffer, desc.len, sddf_cookie); + // @ivanv: handle case in release made + assert(ret == 0); + + if (is_empty) { + // @ivanv: should we be using the notify_reader/notify_writer API? + microkit_notify(dev->sddf_mux_tx_ch); + } + + /* Lastly, move to the next descriptor in the chain */ + desc_idx = desc.next; + } while (desc.flags & VIRTQ_DESC_F_NEXT); + + /* Our final job is to move the available virtq into the used virtq */ + struct virtq_used_elem used_elem; + used_elem.id = desc_head; + /* We did not write to any of the buffers, so len is zero. */ + used_elem.len = 0; + virtq->used->ring[guest_idx % virtq->num] = used_elem; + virtq->used->idx++; + + idx++; + } + + /* Move the available index past every virtq we've processed. */ + // @ivanv: not sure about this line + virtq->avail->idx = idx; + + tx_queue->last_idx = idx; + + dev->data.InterruptStatus = BIT_LOW(0); + // @ivanv: The virq_inject API is poor as it expects a vCPU ID even though + // it doesn't matter for the case of SPIs, which is what virtIO devices use. + bool success = virq_inject(GUEST_VCPU_ID, dev->virq); + assert(success); + + return success; +} + +int virtio_console_handle_rx(struct virtio_device *dev) { + // @ivanv: revisit this whole function, it works but is very hacky. + /* We have received something from the real console driver. + * Our job is to inspect the sDDF used RX ring, and dequeue everything + * we can and give it to the guest driver. + */ + + uintptr_t sddf_buffer = 0; + unsigned int sddf_buffer_len = 0; + void *sddf_cookie = NULL; + int ret = dequeue_used(dev->sddf_rx_ring, &sddf_buffer, &sddf_buffer_len, &sddf_cookie); + assert(!ret); + if (ret != 0) { + LOG_CONSOLE_ERR("could not dequeue from RX used ring\n"); + // @ivanv: handle properly + } + + assert(dev->num_vqs > RX_QUEUE); + struct virtio_queue_handler *rx_queue = &dev->vqs[RX_QUEUE]; + struct virtq *virtq = &rx_queue->virtq; + uint16_t guest_idx = virtq->avail->idx; + size_t idx = rx_queue->last_idx; + + if (idx != guest_idx) { + size_t bytes_written = 0; + + uint16_t desc_head = virtq->avail->ring[idx % virtq->num]; + struct virtq_desc desc; + uint16_t desc_idx = desc_head; + + do { + desc = virtq->desc[desc_idx]; + + size_t bytes_to_copy = (desc.len < sddf_buffer_len) ? desc.len : sddf_buffer_len; + memcpy((char *) desc.addr, (char *) sddf_buffer, bytes_to_copy - bytes_written); + + bytes_written += bytes_to_copy; + } while (bytes_written != sddf_buffer_len); + + struct virtq_used_elem used_elem; + used_elem.id = desc_head; + used_elem.len = bytes_written; + virtq->used->ring[guest_idx % virtq->num] = used_elem; + virtq->used->idx++; + + rx_queue->last_idx++; + + // 3. Inject IRQ to guest + // @ivanv: is setting interrupt status necesary? + dev->data.InterruptStatus = BIT_LOW(0); + bool success = virq_inject(GUEST_VCPU_ID, dev->virq); + assert(success); + } + + // 4. Enqueue sDDF buffer into RX free ring + ret = enqueue_free(dev->sddf_rx_ring, sddf_buffer, BUFFER_SIZE, sddf_cookie); + assert(!ret); + // @ivanv: error handle for release mode + + return -1; +} + +virtio_device_funs_t functions = { + .device_reset = virtio_console_reset, + .get_device_features = virtio_console_get_device_features, + .set_driver_features = virtio_console_set_driver_features, + .get_device_config = virtio_console_get_device_config, + .set_device_config = virtio_console_set_device_config, + .queue_notify = virtio_console_handle_tx, +}; + +void virtio_console_init(struct virtio_device *dev, + struct virtio_queue_handler *vqs, size_t num_vqs, + size_t virq, + ring_handle_t *sddf_rx_ring, ring_handle_t *sddf_tx_ring, size_t sddf_mux_tx_ch) { + // @ivanv: check that num_vqs is greater than the minimum vqs to function? + dev->data.DeviceID = DEVICE_ID_VIRTIO_CONSOLE; + dev->data.VendorID = VIRTIO_MMIO_DEV_VENDOR_ID; + dev->funs = &functions; + dev->vqs = vqs; + dev->num_vqs = num_vqs; + dev->virq = virq; + dev->sddf_rx_ring = sddf_rx_ring; + dev->sddf_tx_ring = sddf_tx_ring; + dev->sddf_mux_tx_ch = sddf_mux_tx_ch; +} diff --git a/src/virtio/console.h b/src/virtio/console.h new file mode 100644 index 00000000..651c47c5 --- /dev/null +++ b/src/virtio/console.h @@ -0,0 +1,95 @@ +/* + * This header is BSD licensed so + * anyone can use the definitions to implement compatible drivers/servers: + * + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of IBM nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL IBM OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Copyright (C) Red Hat, Inc., 2009, 2010, 2011 + * Copyright (C) Amit Shah , 2009, 2010, 2011 + */ +/* + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include +#include "virtio/mmio.h" + +#define RX_QUEUE 0 +#define TX_QUEUE 1 +#define CTL_RX_QUEUE 2 +#define CTL_TX_QUEUE 3 + +/* For the console device to function, we only need an RX queue and TX queue. */ +#define VIRTIO_CONSOLE_NUM_VIRTQ 2 + +/* Feature bits */ +#define VIRTIO_CONSOLE_F_SIZE 0 /* Does host provide console size? */ +#define VIRTIO_CONSOLE_F_MULTIPORT 1 /* Does host provide multiple ports? */ +#define VIRTIO_CONSOLE_F_EMERG_WRITE 2 /* Does host support emergency write? */ + +#define VIRTIO_CONSOLE_BAD_ID (~(uint32_t)0) + +#define VIRTIO_CONSOLE_MAX_PORTS 4 /* Support max 4 ports right now... */ + +#define VIRTIO_CONSOLE_CFG_MAX_PORTS (VIRTIO_PCI_CONFIG_OFF(false) + offsetof(struct virtio_con_cfg, max_nr_ports)) + +struct virtio_console_config { + /* colums of the screens */ + uint16_t cols; + /* rows of the screens */ + uint16_t rows; + /* max. number of ports this device can hold */ + uint32_t max_nr_ports; + /* emergency write register */ + uint32_t emerg_wr; +} __attribute__((packed)); + +/* + * A message that's passed between the Host and the Guest for a + * particular port. + */ +struct virtio_console_control { + uint32_t id; /* Port number */ + uint16_t event; /* The kind of control event (see below) */ + uint16_t value; /* Extra information for the key */ +} __attribute__((packed)); + +/* Some events for control messages */ +#define VIRTIO_CONSOLE_DEVICE_READY 0 +#define VIRTIO_CONSOLE_PORT_ADD 1 +#define VIRTIO_CONSOLE_PORT_REMOVE 2 +#define VIRTIO_CONSOLE_PORT_READY 3 +#define VIRTIO_CONSOLE_CON_PORT 4 +#define VIRTIO_CONSOLE_RESIZE 5 +#define VIRTIO_CONSOLE_PORT_OPEN 6 +#define VIRTIO_CONSOLE_PORT_NAME 7 + +void virtio_console_init(struct virtio_device *dev, + struct virtio_queue_handler *vqs, size_t num_vqs, + size_t virq, + ring_handle_t *sddf_rx_ring, ring_handle_t *sddf_tx_ring, size_t sddf_mux_tx_ch); +int virtio_console_handle_rx(struct virtio_device *dev);