/* * 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; // filter our own features 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) { 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); 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; // ready to go 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); }