From fe11711026bb1bb38d2de97237d89398184cde34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Duval?= Date: Mon, 23 Apr 2018 21:38:38 +0200 Subject: [PATCH] virtio: add driver for the virtio balloon device * how-to for qemu: command line option: -balloon virtio * in the monitor view - to switch the vm size to 500MB: balloon 500 - to display balloon info: info balloon --- .../kernel/bus_managers/virtio/Jamfile | 2 + .../virtio/VirtioBalloonDevice.cpp | 258 ++++++++++++++++++ .../virtio/VirtioBalloonPrivate.h | 83 ++++++ .../bus_managers/virtio/VirtioModule.cpp | 7 + .../bus_managers/virtio/virtio_balloon.cpp | 167 ++++++++++++ .../bus_managers/virtio/virtio_balloon.h | 64 +++++ 6 files changed, 581 insertions(+) create mode 100644 src/add-ons/kernel/bus_managers/virtio/VirtioBalloonDevice.cpp create mode 100644 src/add-ons/kernel/bus_managers/virtio/VirtioBalloonPrivate.h create mode 100644 src/add-ons/kernel/bus_managers/virtio/virtio_balloon.cpp create mode 100644 src/add-ons/kernel/bus_managers/virtio/virtio_balloon.h diff --git a/src/add-ons/kernel/bus_managers/virtio/Jamfile b/src/add-ons/kernel/bus_managers/virtio/Jamfile index ef44b0043a..30dc4524a0 100644 --- a/src/add-ons/kernel/bus_managers/virtio/Jamfile +++ b/src/add-ons/kernel/bus_managers/virtio/Jamfile @@ -7,4 +7,6 @@ KernelAddon virtio : VirtioDevice.cpp VirtioModule.cpp VirtioQueue.cpp + VirtioBalloonDevice.cpp + virtio_balloon.cpp ; diff --git a/src/add-ons/kernel/bus_managers/virtio/VirtioBalloonDevice.cpp b/src/add-ons/kernel/bus_managers/virtio/VirtioBalloonDevice.cpp new file mode 100644 index 0000000000..d106f5bec9 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/virtio/VirtioBalloonDevice.cpp @@ -0,0 +1,258 @@ +/* + * Copyright 2018, Jérôme Duval, jerome.duval@gmail.com. + * Distributed under the terms of the MIT License. + */ + + +#include "VirtioBalloonPrivate.h" + +#include +#include +#include + +#include + +#include "virtio_balloon.h" + + +const char* +get_feature_name(uint32 feature) +{ + switch (feature) { + case VIRTIO_BALLOON_F_MUST_TELL_HOST: + return "must tell host"; + case VIRTIO_BALLOON_F_STATS_VQ: + return "stats vq"; + } + return NULL; +} + + +VirtioBalloonDevice::VirtioBalloonDevice(device_node* node) + : + fNode(node), + fVirtio(NULL), + fVirtioDevice(NULL), + fStatus(B_NO_INIT), + fDesiredSize(0), + fActualSize(0) +{ + CALLED(); + + B_INITIALIZE_SPINLOCK(&fInterruptLock); + fQueueCondition.Init(this, "virtio balloon transfer"); + fConfigCondition.Init(this, "virtio balloon config"); + + get_memory_map(fBuffer, sizeof(fBuffer), &fEntry, 1); + + // get the Virtio device from our parent's parent + device_node* parent = gDeviceManager->get_parent_node(node); + device_node* virtioParent = gDeviceManager->get_parent_node(parent); + gDeviceManager->put_node(parent); + + gDeviceManager->get_driver(virtioParent, (driver_module_info**)&fVirtio, + (void**)&fVirtioDevice); + gDeviceManager->put_node(virtioParent); + + fVirtio->negotiate_features(fVirtioDevice, + 0, &fFeatures, &get_feature_name); + + fStatus = fVirtio->alloc_queues(fVirtioDevice, 2, fVirtioQueues); + if (fStatus != B_OK) { + ERROR("queue allocation failed (%s)\n", strerror(fStatus)); + return; + } + + fStatus = fVirtio->setup_interrupt(fVirtioDevice, _ConfigCallback, this); + if (fStatus != B_OK) { + ERROR("interrupt setup failed (%s)\n", strerror(fStatus)); + return; + } + + fStatus = fVirtio->queue_setup_interrupt(fVirtioQueues[0], + _QueueCallback, fVirtioQueues[0]); + if (fStatus != B_OK) { + ERROR("queue interrupt setup failed (%s)\n", strerror(fStatus)); + return; + } + + fStatus = fVirtio->queue_setup_interrupt(fVirtioQueues[1], + _QueueCallback, fVirtioQueues[1]); + if (fStatus != B_OK) { + ERROR("queue interrupt setup failed (%s)\n", strerror(fStatus)); + return; + } + + fThread = spawn_kernel_thread(&_ThreadEntry, "virtio balloon thread", + B_NORMAL_PRIORITY, this); + if (fThread < 0) { + fStatus = fThread; + return; + } + resume_thread(fThread); +} + + +VirtioBalloonDevice::~VirtioBalloonDevice() +{ + fRunning = false; + if (fThread >= 0) { + int32 result; + wait_for_thread(fThread, &result); + fThread = -1; + } +} + + +status_t +VirtioBalloonDevice::InitCheck() +{ + return fStatus; +} + + +int32 +VirtioBalloonDevice::_ThreadEntry(void* data) +{ + VirtioBalloonDevice* device = (VirtioBalloonDevice*)data; + return device->_Thread(); +} + + +int32 +VirtioBalloonDevice::_Thread() +{ + CALLED(); + + while (fRunning) { + if (fDesiredSize == fActualSize) { + TRACE("waiting for a config change\n"); + ConditionVariableEntry configConditionEntry; + fConfigCondition.Add(&configConditionEntry); + status_t result = configConditionEntry.Wait(B_CAN_INTERRUPT); + if (result != B_OK) + continue; + + fDesiredSize = _DesiredSize(); + TRACE("finished waiting: requested %" B_PRIu32 " instead of %" B_PRIu32 + "\n", fDesiredSize, fActualSize); + } + ::virtio_queue queue; + if (fDesiredSize > fActualSize) { + int32 count = min_c(PAGES_COUNT, fDesiredSize - fActualSize); + TRACE("allocating %" B_PRIu32 " pages\n", count); + queue = fVirtioQueues[0]; + + vm_page_reservation reservation; + vm_page_reserve_pages(&reservation, count, VM_PRIORITY_SYSTEM); + for (int i = 0; i < count; i++) { + //TRACE("allocating page %" B_PRIu32 " pages\n", i); + vm_page* page = vm_page_allocate_page(&reservation, + PAGE_STATE_WIRED); + if (page == NULL) { + TRACE("allocating failed\n"); + count = i; + break; + } + PageInfo* info = new PageInfo; + info->page = page; + fBuffer[i] = (phys_addr_t)page->physical_page_number + >> (PAGE_SHIFT - VIRTIO_BALLOON_PFN_SHIFT); + fPages.Add(info); + } + fEntry.size = count * sizeof(uint32); + fActualSize += count; + } else { + int32 count = min_c(PAGES_COUNT, fActualSize - fDesiredSize); + TRACE("freeing %" B_PRIu32 " pages\n", count); + queue = fVirtioQueues[1]; + + for (int i = 0; i < count; i++) { + PageInfo* info = fPages.RemoveHead(); + if (info == NULL) { + TRACE("remove failed\n"); + count = i; + break; + } + vm_page* page = info->page; + fBuffer[i] = (phys_addr_t)page->physical_page_number + >> (PAGE_SHIFT - VIRTIO_BALLOON_PFN_SHIFT); + vm_page_free(NULL, page); + } + fEntry.size = count * sizeof(uint32); + fActualSize -= count; + } + + ConditionVariableEntry queueConditionEntry; + fQueueCondition.Add(&queueConditionEntry); + + // alloc or release + TRACE("queue request\n"); + status_t result = fVirtio->queue_request(queue, &fEntry, NULL, + queue); + if (result != B_OK) { + ERROR("queueing failed (%s)\n", strerror(result)); + return result; + } + + while (fVirtio->queue_dequeue(queue, NULL) == NULL) { + TRACE("wait for response\n"); + queueConditionEntry.Wait(B_CAN_INTERRUPT); + } + + TRACE("update size\n"); + _UpdateSize(); + } + + return 0; +} + + +void +VirtioBalloonDevice::_ConfigCallback(void* driverCookie) +{ + CALLED(); + VirtioBalloonDevice* device = (VirtioBalloonDevice*)driverCookie; + + SpinLocker locker(device->fInterruptLock); + device->fConfigCondition.NotifyAll(); +} + + +void +VirtioBalloonDevice::_QueueCallback(void* driverCookie, void* cookie) +{ + CALLED(); + VirtioBalloonDevice* device = (VirtioBalloonDevice*)driverCookie; + + SpinLocker locker(device->fInterruptLock); + device->fQueueCondition.NotifyAll(); +} + + +uint32 +VirtioBalloonDevice::_DesiredSize() +{ + CALLED(); + uint32 desiredSize; + + status_t status = fVirtio->read_device_config(fVirtioDevice, + offsetof(struct virtio_balloon_config, num_pages), + &desiredSize, sizeof(desiredSize)); + if (status != B_OK) + return 0; + + return B_LENDIAN_TO_HOST_INT32(desiredSize); +} + + +status_t +VirtioBalloonDevice::_UpdateSize() +{ + CALLED(); + TRACE("_UpdateSize %u\n", fActualSize); + uint32 actualSize = B_HOST_TO_LENDIAN_INT32(fActualSize); + return fVirtio->write_device_config(fVirtioDevice, + offsetof(struct virtio_balloon_config, actual), + &actualSize, sizeof(actualSize)); +} diff --git a/src/add-ons/kernel/bus_managers/virtio/VirtioBalloonPrivate.h b/src/add-ons/kernel/bus_managers/virtio/VirtioBalloonPrivate.h new file mode 100644 index 0000000000..0f8a360a3e --- /dev/null +++ b/src/add-ons/kernel/bus_managers/virtio/VirtioBalloonPrivate.h @@ -0,0 +1,83 @@ +/* + * Copyright 2018, Jérôme Duval, jerome.duval@gmail.com. + * Distributed under the terms of the MIT License. + */ +#ifndef VIRTIO_BALLOON_PRIVATE_H +#define VIRTIO_BALLOON_PRIVATE_H + + +#include +#include +#include +#include + + +//#define TRACE_VIRTIO_BALLOON +#ifdef TRACE_VIRTIO_BALLOON +# define TRACE(x...) dprintf("virtio_balloon: " x) +#else +# define TRACE(x...) ; +#endif +#define ERROR(x...) dprintf("\33[33mvirtio_rng:\33[0m " x) +#define CALLED() TRACE("CALLED %s\n", __PRETTY_FUNCTION__) + + +extern device_manager_info* gDeviceManager; + + +#define PAGES_COUNT 256 + + +struct PageInfo : DoublyLinkedListLinkImpl { + vm_page *page; +}; + + +typedef DoublyLinkedList PageInfoList; + + + +class VirtioBalloonDevice { +public: + VirtioBalloonDevice(device_node* node); + ~VirtioBalloonDevice(); + + status_t InitCheck(); + +private: + static void _ConfigCallback(void* driverCookie); + static void _QueueCallback(void* driverCookie, + void *cookie); + + static int32 _ThreadEntry(void *data); + int32 _Thread(); + + uint32 _DesiredSize(); + status_t _UpdateSize(); + + device_node* fNode; + + virtio_device_interface* fVirtio; + virtio_device* fVirtioDevice; + + status_t fStatus; + uint32 fFeatures; + ::virtio_queue fVirtioQueues[2]; + + physical_entry fEntry; + uint32 fBuffer[PAGES_COUNT]; + + thread_id fThread; + uint32 fDesiredSize; + uint32 fActualSize; + + spinlock fInterruptLock; + ConditionVariable fQueueCondition; + ConditionVariable fConfigCondition; + bool fRunning; + + PageInfoList fPages; +}; + + +#endif // VIRTIO_BALLOON_PRIVATE_H diff --git a/src/add-ons/kernel/bus_managers/virtio/VirtioModule.cpp b/src/add-ons/kernel/bus_managers/virtio/VirtioModule.cpp index 74b77a5dac..e54e094d7c 100644 --- a/src/add-ons/kernel/bus_managers/virtio/VirtioModule.cpp +++ b/src/add-ons/kernel/bus_managers/virtio/VirtioModule.cpp @@ -315,9 +315,16 @@ module_dependency module_dependencies[] = { {} }; + +extern struct driver_module_info sVirtioBalloonDriver; +extern struct driver_module_info sVirtioBalloonDeviceInterface; + + module_info *modules[] = { (module_info *)&virtio_for_controller_module, (module_info *)&virtio_device_module, + (module_info *)&sVirtioBalloonDriver, + (module_info *)&sVirtioBalloonDeviceInterface, NULL }; diff --git a/src/add-ons/kernel/bus_managers/virtio/virtio_balloon.cpp b/src/add-ons/kernel/bus_managers/virtio/virtio_balloon.cpp new file mode 100644 index 0000000000..2874ef0f24 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/virtio/virtio_balloon.cpp @@ -0,0 +1,167 @@ +/* + * Copyright 2018, Jérôme Duval, jerome.duval@gmail.com. + * Distributed under the terms of the MIT License. + */ + + +#include "VirtioBalloonPrivate.h" + +#include +#include +#include + + +#define VIRTIO_BALLOON_CONTROLLER_PRETTY_NAME "Virtio Balloon Device" + +#define VIRTIO_BALLOON_DRIVER_MODULE_NAME "drivers/misc/virtio_balloon/driver_v1" +#define VIRTIO_BALLOON_DEVICE_MODULE_NAME "drivers/misc/virtio_balloon/device_v1" + + +extern device_manager_info *gDeviceManager; + + +// #pragma mark - Device module interface + + +static status_t +virtio_balloon_init_device(device_node *node, void **_cookie) +{ + CALLED(); + + VirtioBalloonDevice *device = new(std::nothrow) + VirtioBalloonDevice(node); + if (device == NULL) + return B_NO_MEMORY; + status_t status = device->InitCheck(); + if (status < B_OK) { + delete device; + return status; + } + + *_cookie = device; + return B_OK; +} + + +static void +virtio_balloon_uninit_device(void *cookie) +{ + CALLED(); + VirtioBalloonDevice *device = (VirtioBalloonDevice*)cookie; + + delete device; +} + + +// #pragma mark - Driver module interface + + +static float +virtio_balloon_supports_device(device_node *parent) +{ + const char *bus; + uint16 deviceType; + + // make sure parent is really the Virtio bus manager + if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false)) + return -1; + + if (strcmp(bus, "virtio")) + return 0.0; + + // check whether it's really a Virtio Entropy Device + if (gDeviceManager->get_attr_uint16(parent, VIRTIO_DEVICE_TYPE_ITEM, + &deviceType, true) != B_OK || deviceType != VIRTIO_DEVICE_ID_BALLOON) + return 0.0; + + TRACE("Virtio Balloon device found!\n"); + + return 0.6f; +} + + +static status_t +virtio_balloon_register_device(device_node *parent) +{ + CALLED(); + + device_attr attrs[] = { + { NULL } + }; + + return gDeviceManager->register_node(parent, VIRTIO_BALLOON_DRIVER_MODULE_NAME, + attrs, NULL, NULL); +} + + +static status_t +virtio_balloon_init_driver(device_node *node, void **_cookie) +{ + CALLED(); + *_cookie = node; + return B_OK; +} + + +static status_t +virtio_balloon_register_child_devices(void *cookie) +{ + CALLED(); + device_node *node = (device_node *)cookie; + + device_attr attrs[] = { + { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, + { string: VIRTIO_BALLOON_CONTROLLER_PRETTY_NAME }}, + { NULL } + }; + + return gDeviceManager->register_node(node, + VIRTIO_BALLOON_DEVICE_MODULE_NAME, attrs, NULL, NULL); +} + + +static status_t +std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + case B_MODULE_UNINIT: + return B_OK; + + default: + return B_ERROR; + } +} + + +driver_module_info sVirtioBalloonDeviceInterface = { + { + VIRTIO_BALLOON_DEVICE_MODULE_NAME, + 0, + std_ops + }, + NULL, // supported devices + NULL, // register node + virtio_balloon_init_device, + virtio_balloon_uninit_device, + NULL, // register child devices + NULL, // rescan + NULL // bus_removed +}; + + +driver_module_info sVirtioBalloonDriver = { + { + VIRTIO_BALLOON_DRIVER_MODULE_NAME, + 0, + std_ops + }, + virtio_balloon_supports_device, + virtio_balloon_register_device, + virtio_balloon_init_driver, + NULL, // uninit_driver, + virtio_balloon_register_child_devices, + NULL, // rescan + NULL, // device_removed +}; + diff --git a/src/add-ons/kernel/bus_managers/virtio/virtio_balloon.h b/src/add-ons/kernel/bus_managers/virtio/virtio_balloon.h new file mode 100644 index 0000000000..b871dfb01c --- /dev/null +++ b/src/add-ons/kernel/bus_managers/virtio/virtio_balloon.h @@ -0,0 +1,64 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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. + * + * $FreeBSD$ + */ + +#ifndef _VIRTIO_BALLOON_H +#define _VIRTIO_BALLOON_H + +/* Feature bits. */ +#define VIRTIO_BALLOON_F_MUST_TELL_HOST 0x1 /* Tell before reclaiming pages */ +#define VIRTIO_BALLOON_F_STATS_VQ 0x2 /* Memory stats virtqueue */ + +/* Size of a PFN in the balloon interface. */ +#define VIRTIO_BALLOON_PFN_SHIFT 12 + +struct virtio_balloon_config { + /* Number of pages host wants Guest to give up. */ + uint32_t num_pages; + + /* Number of pages we've actually got in balloon. */ + uint32_t actual; +}; + +#define VIRTIO_BALLOON_S_SWAP_IN 0 /* Amount of memory swapped in */ +#define VIRTIO_BALLOON_S_SWAP_OUT 1 /* Amount of memory swapped out */ +#define VIRTIO_BALLOON_S_MAJFLT 2 /* Number of major faults */ +#define VIRTIO_BALLOON_S_MINFLT 3 /* Number of minor faults */ +#define VIRTIO_BALLOON_S_MEMFREE 4 /* Total amount of free memory */ +#define VIRTIO_BALLOON_S_MEMTOT 5 /* Total amount of memory */ +#define VIRTIO_BALLOON_S_NR 6 + +struct virtio_balloon_stat { + uint16_t tag; + uint64_t val; +} __packed; + +#endif /* _VIRTIO_BALLOON_H */