* Copyright 2013, 2018, Jérôme Duval, jerome.duval@gmail.com.
* Distributed under the terms of the MIT License.
*/
#include "VirtioPrivate.h"
const char *
virtio_get_feature_name(uint64 feature)
{
switch (feature) {
case VIRTIO_FEATURE_NOTIFY_ON_EMPTY:
return "notify on empty";
case VIRTIO_FEATURE_ANY_LAYOUT:
return "any layout";
case VIRTIO_FEATURE_RING_INDIRECT_DESC:
return "ring indirect";
case VIRTIO_FEATURE_RING_EVENT_IDX:
return "ring event index";
case VIRTIO_FEATURE_BAD_FEATURE:
return "bad feature";
}
return NULL;
}
const char *
virtio_get_device_type_name(uint16 type)
{
switch (type) {
case VIRTIO_DEVICE_ID_NETWORK:
return "network";
case VIRTIO_DEVICE_ID_BLOCK:
return "block";
case VIRTIO_DEVICE_ID_CONSOLE:
return "console";
case VIRTIO_DEVICE_ID_ENTROPY:
return "entropy";
case VIRTIO_DEVICE_ID_BALLOON:
return "balloon";
case VIRTIO_DEVICE_ID_IOMEMORY:
return "io_memory";
case VIRTIO_DEVICE_ID_SCSI:
return "scsi";
case VIRTIO_DEVICE_ID_9P:
return "9p transport";
default:
return "unknown";
}
}
VirtioDevice::VirtioDevice(device_node *node)
:
fNode(node),
fID(0),
fController(NULL),
fCookie(NULL),
fStatus(B_NO_INIT),
fQueues(NULL),
fFeatures(0),
fAlignment(0),
fVirtio1(false),
fConfigHandler(NULL),
fDriverCookie(NULL)
{
CALLED();
device_node *parent = gDeviceManager->get_parent_node(node);
fStatus = gDeviceManager->get_driver(parent,
(driver_module_info **)&fController, &fCookie);
gDeviceManager->put_node(parent);
if (fStatus != B_OK)
return;
fStatus = gDeviceManager->get_attr_uint16(fNode,
VIRTIO_VRING_ALIGNMENT_ITEM, &fAlignment, true);
if (fStatus != B_OK) {
ERROR("alignment missing\n");
return;
}
uint8 version = 0;
if (gDeviceManager->get_attr_uint8(fNode, VIRTIO_VERSION_ITEM, &version, true) == B_OK)
fVirtio1 = version == 1;
fController->set_sim(fCookie, this);
fController->set_status(fCookie, VIRTIO_CONFIG_STATUS_DRIVER);
}
VirtioDevice::~VirtioDevice()
{
if (fQueues != NULL) {
_DestroyQueues(fQueueCount);
}
fController->set_status(fCookie, VIRTIO_CONFIG_STATUS_RESET);
}
status_t
VirtioDevice::InitCheck()
{
return fStatus;
}
status_t
VirtioDevice::NegotiateFeatures(uint64 supported, uint64* negotiated,
const char* (*get_feature_name)(uint64))
{
fFeatures = 0;
status_t status = fController->read_host_features(fCookie, &fFeatures);
if (status != B_OK)
return status;
_DumpFeatures("read features", fFeatures, get_feature_name);
if (fVirtio1) {
supported |= VIRTIO_FEATURE_VERSION_1;
supported &= ~VIRTIO_FEATURE_NOTIFY_ON_EMPTY;
}
fFeatures &= supported;
fFeatures &= (VIRTIO_FEATURE_TRANSPORT_MASK
| VIRTIO_FEATURE_RING_INDIRECT_DESC | VIRTIO_FEATURE_RING_EVENT_IDX
| VIRTIO_FEATURE_VERSION_1);
_DumpFeatures("negotiated features", fFeatures, get_feature_name);
status = fController->write_guest_features(fCookie, fFeatures);
if (status != B_OK)
return status;
if (fVirtio1) {
fController->set_status(fCookie, VIRTIO_CONFIG_STATUS_FEATURES_OK);
if ((fController->get_status(fCookie) & VIRTIO_CONFIG_STATUS_FEATURES_OK) == 0) {
fController->set_status(fCookie, VIRTIO_CONFIG_STATUS_FAILED);
return B_BAD_VALUE;
}
if ((fFeatures & VIRTIO_FEATURE_VERSION_1) == 0) {
fController->set_status(fCookie, VIRTIO_CONFIG_STATUS_FAILED);
return B_BAD_VALUE;
}
}
*negotiated = fFeatures;
return B_OK;
}
status_t
VirtioDevice::ClearFeature(uint64 feature)
{
fFeatures &= ~feature;
return fController->write_guest_features(fCookie, fFeatures);
}
status_t
VirtioDevice::ReadDeviceConfig(uint8 offset, void* buffer, size_t bufferSize)
{
return fController->read_device_config(fCookie, offset, buffer,
bufferSize);
}
status_t
VirtioDevice::WriteDeviceConfig(uint8 offset, const void* buffer,
size_t bufferSize)
{
return fController->write_device_config(fCookie, offset, buffer,
bufferSize);
}
status_t
VirtioDevice::AllocateQueues(size_t count, virtio_queue *queues,
uint16 *requestedSizes)
{
if (count > VIRTIO_VIRTQUEUES_MAX_COUNT || queues == NULL)
return B_BAD_VALUE;
fQueues = new(std::nothrow) VirtioQueue*[count];
if (fQueues == NULL)
return B_NO_MEMORY;
status_t status = B_OK;
fQueueCount = count;
for (size_t index = 0; index < count; index++) {
uint16 size = fController->get_queue_ring_size(fCookie, index);
uint16 requestedSize
= requestedSizes != NULL ? requestedSizes[index] : 0;
if (requestedSize != 0) {
if (requestedSize > size)
status = B_BUFFER_OVERFLOW;
else
size = requestedSize;
}
if (status == B_OK) {
fQueues[index] = new(std::nothrow) VirtioQueue(this, index, size);
queues[index] = fQueues[index];
status = B_NO_MEMORY;
if (fQueues[index] != NULL)
status = fQueues[index]->InitCheck();
}
if (status != B_OK) {
_DestroyQueues(index + 1);
return status;
}
}
return B_OK;
}
void
VirtioDevice::FreeQueues()
{
if (fQueues != NULL)
_DestroyQueues(fQueueCount);
fController->set_status(fCookie, VIRTIO_CONFIG_STATUS_RESET);
fController->set_status(fCookie, VIRTIO_CONFIG_STATUS_DRIVER);
}
status_t
VirtioDevice::SetupInterrupt(virtio_intr_func configHandler, void *driverCookie)
{
fConfigHandler = configHandler;
fDriverCookie = driverCookie;
status_t status = fController->setup_interrupt(fCookie, fQueueCount);
if (status != B_OK)
return status;
fController->set_status(fCookie, VIRTIO_CONFIG_STATUS_DRIVER_OK);
for (size_t index = 0; index < fQueueCount; index++)
fQueues[index]->EnableInterrupt();
return B_OK;
}
status_t
VirtioDevice::FreeInterrupts()
{
for (size_t index = 0; index < fQueueCount; index++)
fQueues[index]->DisableInterrupt();
fController->set_status(fCookie, VIRTIO_CONFIG_STATUS_DRIVER);
return fController->free_interrupt(fCookie);
}
status_t
VirtioDevice::SetupQueue(uint16 queueNumber, phys_addr_t physAddr, phys_addr_t phyAvail,
phys_addr_t phyUsed)
{
return fController->setup_queue(fCookie, queueNumber, physAddr, phyAvail, phyUsed);
}
void
VirtioDevice::NotifyQueue(uint16 queueNumber)
{
fController->notify_queue(fCookie, queueNumber);
}
status_t
VirtioDevice::QueueInterrupt(uint16 queueNumber)
{
if (queueNumber != INT16_MAX) {
if (queueNumber >= fQueueCount)
return B_BAD_VALUE;
return fQueues[queueNumber]->Interrupt();
}
status_t status = B_OK;
for (uint16 i = 0; i < fQueueCount; i++) {
status = fQueues[i]->Interrupt();
if (status != B_OK)
break;
}
return status;
}
status_t
VirtioDevice::ConfigInterrupt()
{
if (fConfigHandler != NULL)
fConfigHandler(fDriverCookie);
return B_OK;
}
void
VirtioDevice::_DestroyQueues(size_t count)
{
for (size_t i = 0; i < count; i++) {
delete fQueues[i];
}
delete[] fQueues;
fQueues = NULL;
}
void
VirtioDevice::_DumpFeatures(const char* title, uint64 features,
const char* (*get_feature_name)(uint64))
{
char features_string[512] = "";
for (uint32 i = 0; i < 64; i++) {
uint64 feature = features & (1ULL << i);
if (feature == 0)
continue;
const char* name = virtio_get_feature_name(feature);
if (name == NULL)
name = get_feature_name(feature);
if (name != NULL) {
strlcat(features_string, "[", sizeof(features_string));
strlcat(features_string, name, sizeof(features_string));
strlcat(features_string, "] ", sizeof(features_string));
}
}
TRACE("%s: %s\n", title, features_string);
}