Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ jobs:
- os: ubuntu-latest
configure_args: -DCMAKE_BUILD_TYPE=Release
build_args: --parallel
artifact_name: tenbox-build-linux
artifact_name: tenbox-build-linux-x64
artifact_path: build/tenbox-vm-runtime
- os: ubuntu-24.04-arm
configure_args: -DCMAKE_BUILD_TYPE=Release
build_args: --parallel
artifact_name: tenbox-build-linux-arm64
artifact_path: build/tenbox-vm-runtime
steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Common sources shared by all platforms
set(TENBOX_CORE_SOURCES
${CMAKE_SOURCE_DIR}/src/core/vmm/vm.cpp
${CMAKE_SOURCE_DIR}/src/core/vmm/vm_io_loop.cpp
${CMAKE_SOURCE_DIR}/src/core/vmm/console_tx_batcher.cpp
${CMAKE_SOURCE_DIR}/src/core/vmm/address_space.cpp
${CMAKE_SOURCE_DIR}/src/core/device/virtio/virtqueue.cpp
${CMAKE_SOURCE_DIR}/src/core/device/virtio/virtio_mmio.cpp
Expand Down
95 changes: 70 additions & 25 deletions src/core/arch/aarch64/aarch64_machine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@
#ifdef __APPLE__
#include "platform/macos/hypervisor/aarch64/hvf_vcpu.h"
#include "platform/macos/hypervisor/aarch64/hvf_vm.h"
#elif defined(__linux__) && defined(__aarch64__)
#include "platform/linux/hypervisor/aarch64/kvm_vcpu.h"
#include "platform/linux/hypervisor/aarch64/kvm_vm.h"
#endif

bool Aarch64Machine::SetupPlatformDevices(
AddressSpace& addr_space,
GuestMemMap& /*mem*/,
HypervisorVm* hv_vm,
std::shared_ptr<ConsolePort> console_port,
VmIoLoop* io_loop,
std::function<void()> shutdown_cb,
std::function<void()> reboot_cb) {

Expand All @@ -30,9 +34,15 @@ bool Aarch64Machine::SetupPlatformDevices(
uart_.SetIrqLevelCallback([this](bool asserted) {
SetIrqLevel(hv_vm_, kUartIrq, asserted);
});
uart_.SetTxCallback([console_port](uint8_t byte) {
if (!console_port) return;
console_port->Write(&byte, 1);
// Thread the per-byte UART stream through a batcher so the downstream
// ConsolePort sees larger chunks instead of N * 1-byte writes.
tx_batcher_ = std::make_unique<ConsoleTxBatcher>(
[console_port](const uint8_t* data, size_t size) {
if (console_port) console_port->Write(data, size);
});
tx_batcher_->AttachIoLoop(io_loop);
uart_.SetTxCallback([this](uint8_t byte) {
tx_batcher_->Append(&byte, 1);
});
addr_space.AddMmioDevice(kUartBase, Pl011::kMmioSize, &uart_);

Expand Down Expand Up @@ -206,20 +216,21 @@ bool Aarch64Machine::LoadKernel(
fdt.AddPropertyString("device_type", "cpu");
fdt.AddPropertyString("compatible", "arm,arm-v8");
fdt.AddPropertyU32("reg", i);
if (config.cpu_count > 1) {
fdt.AddPropertyString("enable-method", "psci");
}
// PSCI is always available (in-kernel PSCI on KVM, userspace
// emulation on HVF) so every CPU — including a single-core config —
// uses "psci" as its enable-method. This also lets the guest use
// PSCI SYSTEM_OFF / SYSTEM_RESET for shutdown/reboot.
fdt.AddPropertyString("enable-method", "psci");
fdt.EndNode();
}
fdt.EndNode();

// PSCI node (for multi-core)
if (config.cpu_count > 1) {
fdt.BeginNode("psci");
fdt.AddPropertyString("compatible", "arm,psci-1.0");
fdt.AddPropertyString("method", "hvc");
fdt.EndNode();
}
// PSCI node (always present so the guest can issue SYSTEM_OFF /
// SYSTEM_RESET, even in single-CPU configurations).
fdt.BeginNode("psci");
fdt.AddPropertyString("compatible", "arm,psci-1.0");
fdt.AddPropertyString("method", "hvc");
fdt.EndNode();

// /timer (ARM generic timer)
fdt.BeginNode("timer");
Expand All @@ -235,10 +246,13 @@ bool Aarch64Machine::LoadKernel(
fdt.AddPropertyEmpty("always-on");
fdt.EndNode();

// /intc (GICv3)
// Use actual redistributor addresses from the hypervisor
// /intc GICv3 by default, with a GICv2 fallback for hosts where the
// in-kernel VGICv3 is unavailable (e.g. Raspberry Pi 5 with GIC-400).
GPA actual_redist_base = kGicRedistBase;
uint32_t redist_total_size = static_cast<uint32_t>(config.cpu_count * 0x20000);
bool use_gic_v2 = false;
GPA gic_v2_cpu_base = 0x08010000ULL;
uint32_t gic_v2_cpu_size = 0x10000;
#ifdef __APPLE__
if (hv_vm_) {
auto* hvf = dynamic_cast<hvf::HvfVm*>(hv_vm_);
Expand All @@ -247,24 +261,48 @@ bool Aarch64Machine::LoadKernel(
redist_total_size = static_cast<uint32_t>(hvf->GetRedistSizePerCpu()) * config.cpu_count;
}
}
#elif defined(__linux__) && defined(__aarch64__)
if (hv_vm_) {
auto* kvm_vm = dynamic_cast<kvm::KvmVm*>(hv_vm_);
if (kvm_vm && kvm_vm->UsesGicV2()) {
use_gic_v2 = true;
gic_v2_cpu_base = kvm::KvmVm::kGicV2CpuBase;
gic_v2_cpu_size = static_cast<uint32_t>(kvm::KvmVm::kGicV2CpuSize);
}
}
#endif

char gic_name[64];
snprintf(gic_name, sizeof(gic_name), "intc@%" PRIx64,
(uint64_t)kGicDistBase);
fdt.BeginNode(gic_name);
fdt.AddPropertyString("compatible", "arm,gic-v3");
if (use_gic_v2) {
fdt.AddPropertyString("compatible", "arm,cortex-a15-gic");
} else {
fdt.AddPropertyString("compatible", "arm,gic-v3");
}
fdt.AddPropertyU32("#interrupt-cells", 3);
fdt.AddPropertyEmpty("interrupt-controller");
fdt.AddPropertyU32("phandle", gic_phandle);
fdt.AddPropertyCells("reg", {
static_cast<uint32_t>(kGicDistBase >> 32),
static_cast<uint32_t>(kGicDistBase & 0xFFFFFFFF),
0, 0x10000, // Distributor: 64 KiB
static_cast<uint32_t>(actual_redist_base >> 32),
static_cast<uint32_t>(actual_redist_base & 0xFFFFFFFF),
0, redist_total_size,
});
if (use_gic_v2) {
fdt.AddPropertyCells("reg", {
static_cast<uint32_t>(kGicDistBase >> 32),
static_cast<uint32_t>(kGicDistBase & 0xFFFFFFFF),
0, 0x10000, // Distributor: 64 KiB (v2 only uses first 4 KiB)
static_cast<uint32_t>(gic_v2_cpu_base >> 32),
static_cast<uint32_t>(gic_v2_cpu_base & 0xFFFFFFFF),
0, gic_v2_cpu_size, // CPU interface (GICC)
});
} else {
fdt.AddPropertyCells("reg", {
static_cast<uint32_t>(kGicDistBase >> 32),
static_cast<uint32_t>(kGicDistBase & 0xFFFFFFFF),
0, 0x10000, // Distributor: 64 KiB
static_cast<uint32_t>(actual_redist_base >> 32),
static_cast<uint32_t>(actual_redist_base & 0xFFFFFFFF),
0, redist_total_size,
});
}
fdt.EndNode();

// Fixed clock for AMBA peripherals (PL011 requires clocks property)
Expand Down Expand Up @@ -380,9 +418,16 @@ bool Aarch64Machine::SetupBootVCpu(HypervisorVCpu* vcpu, uint8_t* /*ram*/) {
return false;
}
return hvf_vcpu->SetupAarch64Boot(kernel_entry_, fdt_gpa_);
#elif defined(__linux__) && defined(__aarch64__)
auto* kvm_vcpu = dynamic_cast<kvm::KvmVCpu*>(vcpu);
if (!kvm_vcpu) {
LOG_ERROR("aarch64: SetupBootVCpu requires KvmVCpu on Linux");
return false;
}
return kvm_vcpu->SetupAarch64Boot(kernel_entry_, fdt_gpa_);
#else
(void)vcpu;
LOG_ERROR("aarch64: SetupBootVCpu called on non-Apple platform");
LOG_ERROR("aarch64: SetupBootVCpu called on unsupported platform");
return false;
#endif
}
Expand Down
8 changes: 8 additions & 0 deletions src/core/arch/aarch64/aarch64_machine.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#pragma once

#include "core/vmm/machine_model.h"
#include "core/vmm/console_tx_batcher.h"
#include "core/arch/aarch64/pl011.h"
#include "core/arch/aarch64/boot.h"
#include "core/device/rtc/pl031_rtc.h"

#include <memory>

// ARM64 virt machine model (Apple Hypervisor.framework).
// Uses GICv3, PL011 UART, FDT boot, and VirtIO MMIO.
class Aarch64Machine final : public MachineModel {
Expand All @@ -16,6 +19,7 @@ class Aarch64Machine final : public MachineModel {
GuestMemMap& mem,
HypervisorVm* hv_vm,
std::shared_ptr<ConsolePort> console_port,
VmIoLoop* io_loop,
std::function<void()> shutdown_cb,
std::function<void()> reboot_cb) override;

Expand All @@ -42,6 +46,10 @@ class Aarch64Machine final : public MachineModel {
private:
Pl011 uart_;
Pl031Rtc rtc_;
// Coalesces per-byte UART tx writes into larger chunks before they
// reach the ConsolePort. unique_ptr so the object is created only
// once SetupPlatformDevices captures the downstream writer.
std::unique_ptr<ConsoleTxBatcher> tx_batcher_;
GPA kernel_entry_ = 0;
GPA fdt_gpa_ = 0;

Expand Down
13 changes: 10 additions & 3 deletions src/core/arch/x86_64/x86_machine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ bool X86Machine::SetupPlatformDevices(
GuestMemMap& /*mem*/,
HypervisorVm* hv_vm,
std::shared_ptr<ConsolePort> console_port,
VmIoLoop* io_loop,
std::function<void()> shutdown_cb,
std::function<void()> reboot_cb) {

Expand All @@ -29,9 +30,15 @@ bool X86Machine::SetupPlatformDevices(
};

uart_.SetIrqCallback([this]() { irq_injector_(4); });
uart_.SetTxCallback([console_port](uint8_t byte) {
if (!console_port) return;
console_port->Write(&byte, 1);
// Thread the per-byte UART stream through a batcher so the downstream
// ConsolePort sees larger chunks instead of N * 1-byte writes.
tx_batcher_ = std::make_unique<ConsoleTxBatcher>(
[console_port](const uint8_t* data, size_t size) {
if (console_port) console_port->Write(data, size);
});
tx_batcher_->AttachIoLoop(io_loop);
uart_.SetTxCallback([this](uint8_t byte) {
tx_batcher_->Append(&byte, 1);
});
addr_space.AddPioDevice(
Uart16550::kCom1Base, Uart16550::kRegCount, &uart_);
Expand Down
6 changes: 6 additions & 0 deletions src/core/arch/x86_64/x86_machine.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#pragma once

#include "core/vmm/machine_model.h"
#include "core/vmm/console_tx_batcher.h"
#include "core/device/serial/uart_16550.h"

#include <memory>
#include "core/device/timer/i8254_pit.h"
#include "core/device/rtc/cmos_rtc.h"
#include "core/device/irq/ioapic.h"
Expand All @@ -22,6 +25,7 @@ class X86Machine final : public MachineModel {
GuestMemMap& mem,
HypervisorVm* hv_vm,
std::shared_ptr<ConsolePort> console_port,
VmIoLoop* io_loop,
std::function<void()> shutdown_cb,
std::function<void()> reboot_cb) override;

Expand Down Expand Up @@ -57,6 +61,8 @@ class X86Machine final : public MachineModel {

private:
Uart16550 uart_;
// Coalesces per-byte UART tx writes before they reach the ConsolePort.
std::unique_ptr<ConsoleTxBatcher> tx_batcher_;
I8254Pit pit_;
SystemControlB sys_ctrl_b_;
CmosRtc rtc_;
Expand Down
33 changes: 32 additions & 1 deletion src/core/device/virtio/virtio_mmio.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
#include "core/device/virtio/virtio_mmio.h"

#if defined(__linux__) || defined(__APPLE__)
#include <unistd.h> // write() for eventfd in IRQFD mode
#endif

static constexpr uint64_t VIRTIO_RING_F_INDIRECT_DESC = (1ULL << 28);
static constexpr uint64_t VIRTIO_F_EVENT_IDX = (1ULL << 29);

namespace {

inline void SignalIrqEventFd(int fd) {
#if defined(__linux__) || defined(__APPLE__)
uint64_t one = 1;
// EFD_NONBLOCK fds may return EAGAIN if the counter saturates (2^64-2
// accumulated unhandled writes) — impossible in practice and harmless.
// Ignore the return value: the only interesting failure would be EBADF.
(void)::write(fd, &one, sizeof(one));
#else
(void)fd;
#endif
}

} // namespace

void VirtioMmioDevice::Init(VirtioDeviceOps* ops, const GuestMemMap& mem) {
ops_ = ops;
mem_ = mem;
Expand Down Expand Up @@ -149,7 +169,10 @@ void VirtioMmioDevice::MmioWrite(uint64_t offset, uint8_t size,
break;
case kInterruptACK: {
uint32_t prev = interrupt_status_.fetch_and(~val, std::memory_order_acq_rel);
if ((prev & ~val) == 0 && irq_level_callback_) {
// In IRQFD mode, deassert is handled by the in-kernel irqchip via
// the EOI + resample path — do not fire the level callback here
// (that would race with the kernel and double-toggle the line).
if (irq_eventfd_ < 0 && (prev & ~val) == 0 && irq_level_callback_) {
irq_level_callback_(false);
}
break;
Expand Down Expand Up @@ -221,6 +244,10 @@ void VirtioMmioDevice::NotifyUsedBuffer(int queue_idx) {
}

interrupt_status_.fetch_or(1, std::memory_order_release); // VIRTIO_MMIO_INT_VRING
if (irq_eventfd_ >= 0) {
SignalIrqEventFd(irq_eventfd_);
return;
}
if (irq_level_callback_)
irq_level_callback_(true);
else if (irq_callback_)
Expand All @@ -230,6 +257,10 @@ void VirtioMmioDevice::NotifyUsedBuffer(int queue_idx) {
void VirtioMmioDevice::NotifyConfigChange() {
config_generation_++;
interrupt_status_.fetch_or(2, std::memory_order_release); // VIRTIO_MMIO_INT_CONFIG
if (irq_eventfd_ >= 0) {
SignalIrqEventFd(irq_eventfd_);
return;
}
if (irq_level_callback_)
irq_level_callback_(true);
else if (irq_callback_)
Expand Down
17 changes: 17 additions & 0 deletions src/core/device/virtio/virtio_mmio.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ class VirtioMmioDevice : public Device {
void SetIrqCallback(IrqCallback cb) { irq_callback_ = std::move(cb); }
void SetIrqLevelCallback(IrqLevelCallback cb) { irq_level_callback_ = std::move(cb); }

// Switch the device to IRQFD mode: instead of invoking the callbacks on
// every notify, write a single 64-bit value to irq_eventfd, letting the
// hypervisor's in-kernel irqchip assert the line directly. In this mode
// the explicit deassert on InterruptACK is skipped as well — deassertion
// is handled by the irqchip EOI + resample path.
//
// Ownership of the fd stays with the caller; it must outlive this device.
void SetIrqEventFd(int fd) { irq_eventfd_ = fd; }

// Snapshot of the internal interrupt_status register. Used by the irqfd
// resample poller to decide whether the device still has a pending
// condition and needs to be re-asserted.
uint32_t GetInterruptStatus() const {
return interrupt_status_.load(std::memory_order_acquire);
}

void MmioRead(uint64_t offset, uint8_t size, uint64_t* value) override;
void MmioWrite(uint64_t offset, uint8_t size, uint64_t value) override;

Expand Down Expand Up @@ -91,6 +107,7 @@ class VirtioMmioDevice : public Device {
GuestMemMap mem_;
IrqCallback irq_callback_;
IrqLevelCallback irq_level_callback_;
int irq_eventfd_ = -1; // IRQFD mode: write to assert; -1 disables.

// Transport state
uint32_t status_ = 0;
Expand Down
Loading
Loading