| // Copyright 2017 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 <hypervisor/balloon.h> |
| |
| #include <limits.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <fbl/auto_lock.h> |
| #include <hypervisor/vcpu.h> |
| #include <hypervisor/virtio.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/port.h> |
| #include <virtio/balloon.h> |
| #include <virtio/virtio.h> |
| #include <virtio/virtio_ids.h> |
| |
| #include "virtio_priv.h" |
| |
| #define QUEUE_SIZE 128u |
| |
| static balloon_t* virtio_device_to_balloon(const virtio_device_t* device) { |
| return (balloon_t*)device->impl; |
| } |
| |
| static zx_status_t decommit_pages(balloon_t* balloon, uint64_t addr, uint64_t len) { |
| return zx_vmo_op_range(balloon->vmo, ZX_VMO_OP_DECOMMIT, addr, len, NULL, 0); |
| } |
| |
| static zx_status_t commit_pages(balloon_t* balloon, uint64_t addr, uint64_t len) { |
| if (balloon->deflate_on_demand) |
| return ZX_OK; |
| return zx_vmo_op_range(balloon->vmo, ZX_VMO_OP_COMMIT, addr, len, NULL, 0); |
| } |
| |
| /* Structure passed to the inflate/deflate queue handler. */ |
| typedef struct queue_ctx { |
| balloon_t* balloon; |
| // Operation to perform on the queue (inflate or deflate). |
| zx_status_t (*op)(balloon_t* balloon, uint64_t addr, uint64_t len); |
| } queue_ctx_t; |
| |
| /* 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. |
| */ |
| static zx_status_t queue_range_op(void* addr, uint32_t len, uint16_t flags, uint32_t* used, |
| void* ctx) { |
| queue_ctx_t* balloon_op_ctx = static_cast<queue_ctx_t*>(ctx); |
| balloon_t* balloon = balloon_op_ctx->balloon; |
| uint32_t* pfns = static_cast<uint32_t*>(addr); |
| uint32_t pfn_count = len / 4; |
| |
| // If the driver writes contiguous PFNs to the array we'll batch them up |
| // when invoking the inflate/deflate operation. |
| uint64_t region_base = 0; |
| uint64_t region_length = 0; |
| for (uint32_t i = 0; i < pfn_count; ++i) { |
| // If we have a contiguous page, increment the length & continue. |
| if (region_length > 0 && (region_base + region_length) == pfns[i]) { |
| region_length++; |
| continue; |
| } |
| |
| // If we have an existing region; invoke the inflate/deflate op. |
| if (region_length > 0) { |
| zx_status_t status = balloon_op_ctx->op(balloon, |
| region_base * VIRTIO_BALLOON_PAGE_SIZE, |
| region_length * VIRTIO_BALLOON_PAGE_SIZE); |
| if (status != ZX_OK) |
| return status; |
| } |
| |
| // Create a new region. |
| region_base = pfns[i]; |
| region_length = 1; |
| } |
| |
| // Handle the last region. |
| if (region_length > 0) { |
| zx_status_t status = balloon_op_ctx->op(balloon, region_base * VIRTIO_BALLOON_PAGE_SIZE, |
| region_length * VIRTIO_BALLOON_PAGE_SIZE); |
| if (status != ZX_OK) |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| static zx_status_t handle_queue_notify(balloon_t* balloon, uint16_t queue_sel) { |
| queue_ctx_t ctx; |
| switch (queue_sel) { |
| case VIRTIO_BALLOON_Q_STATSQ: |
| return ZX_OK; |
| case VIRTIO_BALLOON_Q_INFLATEQ: |
| ctx.op = &decommit_pages; |
| break; |
| case VIRTIO_BALLOON_Q_DEFLATEQ: |
| ctx.op = &commit_pages; |
| break; |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| } |
| ctx.balloon = balloon; |
| return virtio_queue_handler(&balloon->queues[queue_sel], &queue_range_op, &ctx); |
| } |
| |
| static zx_status_t balloon_queue_notify(virtio_device_t* device, uint16_t queue_sel) { |
| zx_status_t status; |
| balloon_t* balloon = virtio_device_to_balloon(device); |
| do { |
| status = handle_queue_notify(balloon, queue_sel); |
| } while (status == ZX_ERR_NEXT); |
| return status; |
| } |
| |
| static zx_status_t balloon_read(const virtio_device_t* device, uint16_t port, uint8_t access_size, |
| zx_vcpu_io_t* vcpu_io) { |
| balloon_t* balloon = virtio_device_to_balloon(device); |
| |
| fbl::AutoLock lock(&balloon->mutex); |
| return virtio_device_config_read(device, &balloon->config, port, access_size, vcpu_io); |
| } |
| |
| static zx_status_t balloon_write(virtio_device_t* device, uint16_t port, |
| const zx_vcpu_io_t* io) { |
| balloon_t* balloon = virtio_device_to_balloon(device); |
| |
| fbl::AutoLock lock(&balloon->mutex); |
| return virtio_device_config_write(device, &balloon->config, port, io); |
| } |
| |
| static const virtio_device_ops_t kBalloonVirtioDeviceOps = { |
| .read = &balloon_read, |
| .write = &balloon_write, |
| .queue_notify = &balloon_queue_notify, |
| }; |
| |
| void balloon_init(balloon_t* balloon, uintptr_t guest_physmem_addr, size_t guest_physmem_size, |
| zx_handle_t guest_physmem_vmo) { |
| memset(balloon, 0, sizeof(*balloon)); |
| |
| // Virt queue initialization. |
| for (int i = 0; i < VIRTIO_BALLOON_Q_COUNT; ++i) { |
| virtio_queue_t* queue = &balloon->queues[i]; |
| queue->size = QUEUE_SIZE; |
| queue->virtio_device = &balloon->virtio_device; |
| } |
| |
| // Setup virtio device. |
| balloon->virtio_device.device_id = VIRTIO_ID_BALLOON; |
| balloon->virtio_device.config_size = sizeof(virtio_balloon_config_t); |
| balloon->virtio_device.impl = balloon; |
| balloon->virtio_device.num_queues = VIRTIO_BALLOON_Q_COUNT; |
| balloon->virtio_device.queues = balloon->queues; |
| balloon->virtio_device.ops = &kBalloonVirtioDeviceOps; |
| balloon->virtio_device.guest_physmem_addr = guest_physmem_addr; |
| balloon->virtio_device.guest_physmem_size = guest_physmem_size; |
| balloon->virtio_device.features = VIRTIO_BALLOON_F_STATS_VQ | VIRTIO_BALLOON_F_DEFLATE_ON_OOM; |
| |
| // Device configuration values. |
| balloon->vmo = guest_physmem_vmo; |
| balloon->config.num_pages = 0; |
| balloon->config.actual = 0; |
| |
| // PCI Transport. |
| virtio_pci_init(&balloon->virtio_device); |
| } |
| |
| static void wait_for_stats_buffer(balloon_t* balloon, virtio_queue_t* stats_queue) { |
| if (!balloon->stats.has_buffer) { |
| virtio_queue_wait(stats_queue, &balloon->stats.desc_index); |
| balloon->stats.has_buffer = true; |
| } |
| } |
| |
| zx_status_t balloon_request_stats(balloon_t* balloon, balloon_stats_fn_t handler, void* ctx) { |
| zx_status_t status; |
| virtio_queue_t* stats_queue = &balloon->queues[VIRTIO_BALLOON_Q_STATSQ]; |
| |
| // stats.mutex needs to be held during the entire time the guest is |
| // processing the buffer since we need to make sure no other threads |
| // can grab the returned stats buffer before we process it. |
| mtx_lock(&balloon->stats.mutex); |
| |
| // We need an initial buffer we can return to return to the device to |
| // request stats from the device. This should be immediately available in |
| // the common case but we can race the driver for the initial buffer. |
| wait_for_stats_buffer(balloon, stats_queue); |
| |
| // We have a buffer. We need to return it to the driver. It'll populate |
| // a new buffer with stats and then send it back to us. |
| balloon->stats.has_buffer = false; |
| virtio_queue_return(stats_queue, balloon->stats.desc_index, 0); |
| status = virtio_device_notify(&balloon->virtio_device); |
| if (status != ZX_OK) { |
| mtx_unlock(&balloon->stats.mutex); |
| return status; |
| } |
| wait_for_stats_buffer(balloon, stats_queue); |
| |
| virtio_desc_t desc; |
| status = virtio_queue_read_desc(stats_queue, balloon->stats.desc_index, &desc); |
| if (status != ZX_OK) { |
| mtx_unlock(&balloon->stats.mutex); |
| return status; |
| } |
| |
| if ((desc.len % sizeof(virtio_balloon_stat_t)) != 0) { |
| mtx_unlock(&balloon->stats.mutex); |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| |
| // Invoke the handler on the stats. |
| auto stats = static_cast<const virtio_balloon_stat_t*>(desc.addr); |
| size_t stats_count = desc.len / sizeof(virtio_balloon_stat_t); |
| handler(stats, stats_count, ctx); |
| mtx_unlock(&balloon->stats.mutex); |
| |
| // Note we deliberately do not return the buffer here. This will be done to |
| // initiate the next stats request. |
| return ZX_OK; |
| } |
| |
| zx_status_t balloon_update_num_pages(balloon_t* balloon, uint32_t num_pages) { |
| mtx_lock(&balloon->mutex); |
| balloon->config.num_pages = num_pages; |
| mtx_unlock(&balloon->mutex); |
| |
| // Send a config change interrupt to the guest. |
| virtio_device_t* virtio_device = &balloon->virtio_device; |
| mtx_lock(&virtio_device->mutex); |
| virtio_device->isr_status |= VIRTIO_ISR_DEVICE; |
| mtx_unlock(&virtio_device->mutex); |
| return virtio_device_notify(virtio_device); |
| } |