Skip to content

Commit

Permalink
lib: add implementation of virtIO console device
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan-Velickovic committed Nov 10, 2023
1 parent 0bedada commit aac1506
Show file tree
Hide file tree
Showing 2 changed files with 370 additions and 0 deletions.
275 changes: 275 additions & 0 deletions src/virtio/console.c
Original file line number Diff line number Diff line change
@@ -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;
}
95 changes: 95 additions & 0 deletions src/virtio/console.h
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>, 2009, 2010, 2011
*/
/*
* SPDX-License-Identifier: BSD-3-Clause
*/

#pragma once

#include <stdint.h>
#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);

0 comments on commit aac1506

Please sign in to comment.