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
This commit is contained in:
Jérôme Duval 2018-04-23 21:38:38 +02:00 committed by Adrien Destugues
parent 2284eb4875
commit fe11711026
6 changed files with 581 additions and 0 deletions

View File

@ -7,4 +7,6 @@ KernelAddon virtio :
VirtioDevice.cpp
VirtioModule.cpp
VirtioQueue.cpp
VirtioBalloonDevice.cpp
virtio_balloon.cpp
;

View File

@ -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 <new>
#include <stdlib.h>
#include <string.h>
#include <util/AutoLock.h>
#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));
}

View File

@ -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 <condition_variable.h>
#include <lock.h>
#include <virtio.h>
#include <vm/vm_page.h>
//#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<PageInfo> {
vm_page *page;
};
typedef DoublyLinkedList<PageInfo> 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

View File

@ -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
};

View File

@ -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 <new>
#include <stdlib.h>
#include <string.h>
#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
};

View File

@ -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 */