-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lib: add implementation of virtIO console device
- Loading branch information
1 parent
0bedada
commit aac1506
Showing
2 changed files
with
370 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |