blob: 8d2eb25207a64122f3c1d64f0ab0143d61c823d0 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fit/defer.h>
#include <lib/syslog/cpp/log_settings.h>
#include <lib/trace-provider/provider.h>
#include <virtio/balloon.h>
#include "src/virtualization/bin/vmm/device/device_base.h"
#include "src/virtualization/bin/vmm/device/stream_base.h"
// From Virtio 1.0, Section 5.5.6: This value is historical, and independent
// of the guest page size.
static constexpr uint32_t kPageSize = 4096;
// Limit the number of callbacks so that the device process can not be exhausted
// of memory by requests for memory statistics.
static constexpr size_t kCallbackLimit = 8;
using GetMemStatsCallback = fuchsia::virtualization::hardware::VirtioBalloon::GetMemStatsCallback;
enum class Queue : uint16_t {
INFLATE = 0,
DEFLATE = 1,
STATS = 2,
};
// Stream for inflate and deflate queues.
class BalloonStream : public StreamBase {
public:
void DoBalloon(const zx::vmo& vmo, uint32_t op) {
for (; queue_.NextChain(&chain_); chain_.Return()) {
while (chain_.NextDescriptor(&desc_)) {
zx_status_t status = DoOperation(vmo, op);
FX_CHECK(status == ZX_OK) << "Operation failed " << status;
}
}
}
private:
// Handle balloon inflate/deflate requests. From Virtio 1.0, Section 5.5.6:
//
// To supply memory to the balloon (aka. inflate):
// (a) The driver constructs an array of addresses of unused memory pages.
// These addresses are divided by 4096 and the descriptor describing the
// resulting 32-bit array is added to the inflateq.
//
// To remove memory from the balloon (aka. deflate):
// (a) The driver constructs an array of addresses of memory pages it has
// previously given to the balloon, as described above. This descriptor
// is added to the deflateq.
// (b) If the VIRTIO_BALLOON_F_MUST_TELL_HOST feature is negotiated, the
// guest informs the device of pages before it uses them.
// (c) Otherwise, the guest is allowed to re-use pages previously given to
// the balloon before the device has acknowledged their withdrawal.
zx_status_t DoOperation(const zx::vmo& vmo, uint32_t op) {
auto pfns = static_cast<uint32_t*>(desc_.addr);
auto num_pfns = desc_.len / 4;
// If the driver writes contiguous PFNs, we will combine them into runs.
uint64_t base = 0;
uint64_t run = 0;
for (uint32_t i = 0; i < num_pfns; i++) {
if (run > 0) {
// If this is part of the current run, extend it.
if (base + run == pfns[i]) {
run++;
continue;
}
// We have completed a run, so process it before starting a new run.
zx_status_t status = vmo.op_range(op, base * kPageSize, run * kPageSize, nullptr, 0);
if (status != ZX_OK) {
return status;
}
}
// Start a new run.
base = pfns[i];
run = 1;
}
if (run == 0) {
return ZX_OK;
}
// Process the final run.
return vmo.op_range(op, base * kPageSize, run * kPageSize, nullptr, 0);
}
};
// Stream for stats queue.
class StatsStream : public StreamBase {
public:
void GetMemStats(GetMemStatsCallback callback) {
if (callbacks_.size() >= kCallbackLimit) {
// If we have reached our limit for queued callbacks, return.
callback(ZX_ERR_SHOULD_WAIT, cpp17::nullopt);
return;
} else if (!chain_.IsValid()) {
// If this is the first time memory statistics are requested, fetch a
// descriptor chain from the queue.
if (!queue_.NextChain(&chain_)) {
// If we do not have a descriptor chain in the queue, the device is not
// ready, therefore return.
callback(ZX_ERR_SHOULD_WAIT, cpp17::nullopt);
return;
}
}
chain_.Return();
callbacks_.emplace_back(std::move(callback));
}
void DoStats() {
if (callbacks_.empty()) {
return;
}
zx_status_t status = ZX_ERR_IO_DATA_INTEGRITY;
std::vector<fuchsia::virtualization::MemStat> mem_stats;
if (queue_.NextChain(&chain_) && chain_.NextDescriptor(&desc_) &&
desc_.len % sizeof(virtio_balloon_stat_t) == 0) {
auto stats = static_cast<virtio_balloon_stat_t*>(desc_.addr);
size_t len = desc_.len / sizeof(virtio_balloon_stat_t);
for (size_t i = 0; i < len; i++) {
mem_stats.push_back({
.tag = stats[i].tag,
.val = stats[i].val,
});
}
status = ZX_OK;
}
for (auto& callback : callbacks_) {
callback(status, fidl::Clone(mem_stats));
}
callbacks_.clear();
}
private:
std::vector<GetMemStatsCallback> callbacks_;
};
// Implementation of a virtio-balloon device.
class VirtioBalloonImpl : public DeviceBase<VirtioBalloonImpl>,
public fuchsia::virtualization::hardware::VirtioBalloon {
public:
VirtioBalloonImpl(sys::ComponentContext* context) : DeviceBase(context) {}
// |fuchsia::virtualization::hardware::VirtioDevice|
void NotifyQueue(uint16_t queue) override {
switch (static_cast<Queue>(queue)) {
case Queue::INFLATE:
inflate_stream_.DoBalloon(phys_mem_.vmo(), ZX_VMO_OP_DECOMMIT);
break;
case Queue::DEFLATE:
deflate_stream_.DoBalloon(phys_mem_.vmo(), ZX_VMO_OP_COMMIT);
break;
case Queue::STATS:
stats_stream_.DoStats();
break;
default:
FX_CHECK(false) << "Queue index " << queue << " out of range";
__UNREACHABLE;
}
}
private:
// |fuchsia::virtualization::hardware::VirtioBalloon|
void Start(fuchsia::virtualization::hardware::StartInfo start_info,
StartCallback callback) override {
auto deferred = fit::defer(std::move(callback));
PrepStart(std::move(start_info));
inflate_stream_.Init(
phys_mem_, fit::bind_member<zx_status_t, DeviceBase>(this, &VirtioBalloonImpl::Interrupt));
deflate_stream_.Init(
phys_mem_, fit::bind_member<zx_status_t, DeviceBase>(this, &VirtioBalloonImpl::Interrupt));
stats_stream_.Init(
phys_mem_, fit::bind_member<zx_status_t, DeviceBase>(this, &VirtioBalloonImpl::Interrupt));
}
// |fuchsia::virtualization::hardware::VirtioBalloon|
void GetMemStats(GetMemStatsCallback callback) override {
if (!(negotiated_features_ & VIRTIO_BALLOON_F_STATS_VQ)) {
// If memory statistics are not supported, return.
callback(ZX_ERR_NOT_SUPPORTED, cpp17::nullopt);
} else {
stats_stream_.GetMemStats(std::move(callback));
}
}
// |fuchsia::virtualization::hardware::VirtioDevice|
void ConfigureQueue(uint16_t queue, uint16_t size, zx_gpaddr_t desc, zx_gpaddr_t avail,
zx_gpaddr_t used, ConfigureQueueCallback callback) override {
auto deferred = fit::defer(std::move(callback));
switch (static_cast<Queue>(queue)) {
case Queue::INFLATE:
inflate_stream_.Configure(size, desc, avail, used);
break;
case Queue::DEFLATE:
deflate_stream_.Configure(size, desc, avail, used);
break;
case Queue::STATS:
stats_stream_.Configure(size, desc, avail, used);
break;
default:
FX_CHECK(false) << "Queue index " << queue << " out of range";
__UNREACHABLE;
}
}
// |fuchsia::virtualization::hardware::VirtioDevice|
void Ready(uint32_t negotiated_features, ReadyCallback callback) override {
negotiated_features_ = negotiated_features;
callback();
}
uint32_t negotiated_features_;
BalloonStream inflate_stream_;
BalloonStream deflate_stream_;
StatsStream stats_stream_;
};
int main(int argc, char** argv) {
syslog::SetTags({"virtio_balloon"});
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
trace::TraceProviderWithFdio trace_provider(loop.dispatcher());
std::unique_ptr<sys::ComponentContext> context =
sys::ComponentContext::CreateAndServeOutgoingDirectory();
VirtioBalloonImpl virtio_balloon(context.get());
return loop.Run();
}