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
26 changes: 20 additions & 6 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,24 @@ on:

jobs:
build-cpp:
name: Build C++ Project
name: Build C++ Project (${{ matrix.os }})
if: >-
github.event_name == 'pull_request'
runs-on: windows-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
configure_args: -A x64 -DCMAKE_BUILD_TYPE=Release
build_args: --config Release --parallel
artifact_name: tenbox-build-windows
artifact_path: build/Release/*.exe
- os: ubuntu-latest
configure_args: -DCMAKE_BUILD_TYPE=Release
build_args: --parallel
artifact_name: tenbox-build-linux
artifact_path: build/tenbox-vm-runtime
steps:
- uses: actions/checkout@v4

Expand All @@ -25,18 +39,18 @@ jobs:

- name: Configure CMake
if: steps.changes.outputs.src == 'true'
run: cmake -B build -A x64 -DCMAKE_BUILD_TYPE=Release
run: cmake -B build ${{ matrix.configure_args }}

- name: Build
if: steps.changes.outputs.src == 'true'
run: cmake --build build --config Release --parallel
run: cmake --build build ${{ matrix.build_args }}

- name: Upload build artifacts
if: steps.changes.outputs.src == 'true'
uses: actions/upload-artifact@v4
with:
name: tenbox-build
path: build/Release/*.exe
name: ${{ matrix.artifact_name }}
path: ${{ matrix.artifact_path }}

- name: Skip notice
if: steps.changes.outputs.src != 'true'
Expand Down
14 changes: 14 additions & 0 deletions scripts/docker/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,21 @@ fi

WORK_DIR="/tmp/tenbox-${ARCH}-${TARGET}"

# Rootfs builds require loop-mounting rootfs.raw inside the container.
# Even with --privileged, Docker does not share the host's /dev by default,
# so losetup inside the container may fail to create/allocate loop devices.
# Bind-mount /dev and ensure the loop module is available on the host.
DEV_ARGS=()
if needs_qemu_img "$TARGET"; then
if [ ! -e /dev/loop-control ]; then
echo "Loading 'loop' kernel module on host..."
sudo modprobe loop || true
fi
DEV_ARGS=(-v /dev:/dev)
fi

exec docker run --rm --privileged \
"${DEV_ARGS[@]}" \
-v "$PROJECT_ROOT:/workspace" \
-e ROOT_PASSWORD="${ROOT_PASSWORD:-tenbox}" \
-e USER_NAME="${USER_NAME:-tenbox}" \
Expand Down
47 changes: 24 additions & 23 deletions src/core/arch/aarch64/aarch64_machine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "core/arch/aarch64/fdt_builder.h"
#include "core/device/irq/gicv3.h"
#include "core/vmm/vm.h"
#include <cinttypes>
#include <cstring>
#include <cstdio>
#include <random>
Expand Down Expand Up @@ -47,8 +48,9 @@ bool Aarch64Machine::SetupPlatformDevices(
auto* hvf = dynamic_cast<hvf::HvfVm*>(hv_vm);
if (hvf && hvf->UsesSoftGic()) {
hvf->GetSoftGic()->RegisterDevices(addr_space, kGicDistBase, kGicRedistBase);
LOG_INFO("aarch64: software GICv3 MMIO registered at dist=0x%llx redist=0x%llx",
(unsigned long long)kGicDistBase, (unsigned long long)kGicRedistBase);
LOG_INFO("aarch64: software GICv3 MMIO registered at dist=0x%" PRIx64
" redist=0x%" PRIx64,
(uint64_t)kGicDistBase, (uint64_t)kGicRedistBase);
}
}
#endif
Expand Down Expand Up @@ -126,17 +128,16 @@ bool Aarch64Machine::LoadKernel(
fclose(fp);

if (read != static_cast<size_t>(initrd_size)) {
LOG_ERROR("aarch64: short initrd read (%zu / %llu)", read,
(unsigned long long)initrd_size);
LOG_ERROR("aarch64: short initrd read (%zu / %" PRIu64 ")", read,
initrd_size);
return false;
}

initrd_start = Layout::kRamBase + initrd_offset;
initrd_end = initrd_start + initrd_size;

LOG_INFO("aarch64: initrd loaded at GPA 0x%llx (%llu bytes)",
(unsigned long long)initrd_start,
(unsigned long long)initrd_size);
LOG_INFO("aarch64: initrd loaded at GPA 0x%" PRIx64 " (%" PRIu64 " bytes)",
(uint64_t)initrd_start, initrd_size);
}

// Build FDT
Expand All @@ -162,8 +163,8 @@ bool Aarch64Machine::LoadKernel(
fdt.AddPropertyU64("linux,initrd-end", initrd_end);
}
char stdout_path[64];
snprintf(stdout_path, sizeof(stdout_path), "/pl011@%llx",
(unsigned long long)kUartBase);
snprintf(stdout_path, sizeof(stdout_path), "/pl011@%" PRIx64,
(uint64_t)kUartBase);
fdt.AddPropertyString("stdout-path", stdout_path);

// Provide host entropy so the guest kernel can initialize CRNG
Expand All @@ -182,8 +183,8 @@ bool Aarch64Machine::LoadKernel(

// /memory
char mem_name[32];
snprintf(mem_name, sizeof(mem_name), "memory@%llx",
(unsigned long long)Layout::kRamBase);
snprintf(mem_name, sizeof(mem_name), "memory@%" PRIx64,
(uint64_t)Layout::kRamBase);
fdt.BeginNode(mem_name);
fdt.AddPropertyString("device_type", "memory");
fdt.AddPropertyCells("reg", {
Expand Down Expand Up @@ -249,8 +250,8 @@ bool Aarch64Machine::LoadKernel(
#endif

char gic_name[64];
snprintf(gic_name, sizeof(gic_name), "intc@%llx",
(unsigned long long)kGicDistBase);
snprintf(gic_name, sizeof(gic_name), "intc@%" PRIx64,
(uint64_t)kGicDistBase);
fdt.BeginNode(gic_name);
fdt.AddPropertyString("compatible", "arm,gic-v3");
fdt.AddPropertyU32("#interrupt-cells", 3);
Expand Down Expand Up @@ -278,8 +279,8 @@ bool Aarch64Machine::LoadKernel(

// /pl011 UART (AMBA PL011 — needs arm,primecell compat and clocks)
char uart_name[64];
snprintf(uart_name, sizeof(uart_name), "pl011@%llx",
(unsigned long long)kUartBase);
snprintf(uart_name, sizeof(uart_name), "pl011@%" PRIx64,
(uint64_t)kUartBase);
fdt.BeginNode(uart_name);
{
const char* compat[] = {"arm,pl011", "arm,primecell"};
Expand Down Expand Up @@ -307,8 +308,8 @@ bool Aarch64Machine::LoadKernel(
// PL031 RTC (arm,pl031 — PrimeCell, needs clocks like PL011)
{
char rtc_name[64];
snprintf(rtc_name, sizeof(rtc_name), "pl031@%llx",
(unsigned long long)kRtcBase);
snprintf(rtc_name, sizeof(rtc_name), "pl031@%" PRIx64,
(uint64_t)kRtcBase);
fdt.BeginNode(rtc_name);
{
const char* compat[] = {"arm,pl031", "arm,primecell"};
Expand All @@ -335,8 +336,8 @@ bool Aarch64Machine::LoadKernel(
for (size_t i = 0; i < virtio_slots.size(); i++) {
const auto& slot = virtio_slots[i];
char name[64];
snprintf(name, sizeof(name), "virtio_mmio@%llx",
(unsigned long long)slot.mmio_base);
snprintf(name, sizeof(name), "virtio_mmio@%" PRIx64,
(uint64_t)slot.mmio_base);
fdt.BeginNode(name);
fdt.AddPropertyString("compatible", "virtio,mmio");
fdt.AddPropertyCells("reg", {
Expand All @@ -357,14 +358,14 @@ bool Aarch64Machine::LoadKernel(
// Place FDT at the start of guest RAM
fdt_gpa_ = Layout::kFdtBase;
if (dtb.size() > Layout::kFdtMaxSize) {
LOG_ERROR("aarch64: FDT too large (%zu bytes, max %llu)",
dtb.size(), (unsigned long long)Layout::kFdtMaxSize);
LOG_ERROR("aarch64: FDT too large (%zu bytes, max %" PRIu64 ")",
dtb.size(), (uint64_t)Layout::kFdtMaxSize);
return false;
}

memcpy(mem.base, dtb.data(), dtb.size());
LOG_INFO("aarch64: FDT placed at GPA 0x%llx (%zu bytes)",
(unsigned long long)fdt_gpa_, dtb.size());
LOG_INFO("aarch64: FDT placed at GPA 0x%" PRIx64 " (%zu bytes)",
(uint64_t)fdt_gpa_, dtb.size());

return true;
}
Expand Down
5 changes: 2 additions & 3 deletions src/core/arch/aarch64/boot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,8 @@ GPA LoadLinuxImage(BootConfig& config) {
return 0;
}

LOG_INFO("aarch64: kernel loaded at GPA 0x%llx (%ld bytes, text_offset=0x%llx)",
(unsigned long long)kernel_gpa, file_size,
(unsigned long long)text_offset);
LOG_INFO("aarch64: kernel loaded at GPA 0x%" PRIx64 " (%ld bytes, text_offset=0x%" PRIx64 ")",
(uint64_t)kernel_gpa, file_size, text_offset);

return kernel_gpa;
}
Expand Down
5 changes: 3 additions & 2 deletions src/core/arch/x86_64/acpi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,9 @@ GPA BuildAcpiTables(uint8_t* ram, uint32_t num_cpus,
rsdp->checksum = AcpiChecksum(rsdp_base, 20);
rsdp->extended_checksum = AcpiChecksum(rsdp_base, sizeof(AcpiRsdp));

LOG_INFO("ACPI tables: RSDP@0x%llX XSDT@0x%llX MADT@0x%llX "
"FADT@0x%llX DSDT@0x%llX (%u virtio-mmio dev%s)",
LOG_INFO("ACPI tables: RSDP@0x%" PRIX64 " XSDT@0x%" PRIX64
" MADT@0x%" PRIX64 " FADT@0x%" PRIX64 " DSDT@0x%" PRIX64
" (%u virtio-mmio dev%s)",
AcpiLayout::kRsdp, AcpiLayout::kXsdt, AcpiLayout::kMadt,
AcpiLayout::kFadt, AcpiLayout::kDsdt,
(uint32_t)virtio_devs.size(),
Expand Down
12 changes: 6 additions & 6 deletions src/core/arch/x86_64/boot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ uint64_t LoadLinuxKernel(const BootConfig& config) {

// Copy protected-mode kernel to 1MB
memcpy(ram + Layout::kKernelBase, kernel.data() + setup_size, kernel_size);
LOG_INFO("Kernel loaded at GPA 0x%llX (%u bytes)",
Layout::kKernelBase, kernel_size);
LOG_INFO("Kernel loaded at GPA 0x%" PRIX64 " (%u bytes)",
(uint64_t)Layout::kKernelBase, kernel_size);

// --- Set up boot_params (zero page) ---
uint8_t* bp = ram + Layout::kBootParams;
Expand Down Expand Up @@ -110,7 +110,7 @@ uint64_t LoadLinuxKernel(const BootConfig& config) {
*reinterpret_cast<uint32_t*>(bp + BootOffset::kRamdiskSize) =
static_cast<uint32_t>(initrd.size());

LOG_INFO("Initrd loaded at GPA 0x%llX (%zu bytes)",
LOG_INFO("Initrd loaded at GPA 0x%" PRIX64 " (%zu bytes)",
initrd_addr, initrd.size());
}

Expand All @@ -126,12 +126,12 @@ uint64_t LoadLinuxKernel(const BootConfig& config) {
// Entry 2 (optional): high memory above 4 GiB
if (mem.high_size > 0) {
e820[e820_count++] = {mem.high_base, mem.high_size, kE820Ram};
LOG_INFO("E820: [0-0x9FFFF RAM] [0x100000-0x%llX RAM] "
"[0x%llX-0x%llX RAM]",
LOG_INFO("E820: [0-0x9FFFF RAM] [0x100000-0x%" PRIX64 " RAM] "
"[0x%" PRIX64 "-0x%" PRIX64 " RAM]",
mem.low_size - 1,
mem.high_base, mem.high_base + mem.high_size - 1);
} else {
LOG_INFO("E820: [0-0x9FFFF RAM] [0x100000-0x%llX RAM]",
LOG_INFO("E820: [0-0x9FFFF RAM] [0x100000-0x%" PRIX64 " RAM]",
mem.low_size - 1);
}
bp[BootOffset::kE820Entries] = e820_count;
Expand Down
11 changes: 11 additions & 0 deletions src/core/arch/x86_64/x86_machine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ bool X86Machine::SetupBootVCpu(HypervisorVCpu* vcpu, uint8_t* ram) {
}

void X86Machine::InjectIrq(HypervisorVm* hv_vm, uint8_t irq) {
// Hypervisors with an in-kernel irqchip (KVM) handle the IOAPIC/PIC/LAPIC
// in the kernel. InjectIrq() has pulse semantics (one-shot edge IRQ), so
// pulse the GSI line: assert then de-assert. Without the de-assert, an
// edge-triggered line stays latched high and no further IRQs re-fire on
// the same GSI (breaks UART THRE, PIT tick, etc.).
if (hv_vm->AssertIrq(irq, true)) {
hv_vm->AssertIrq(irq, false);
return;
}

uint64_t rte = 0;
if (!ioapic_.GetRedirEntry(irq, &rte)) return;

Expand All @@ -129,6 +139,7 @@ void X86Machine::InjectIrq(HypervisorVm* hv_vm, uint8_t irq) {
}

void X86Machine::SetIrqLevel(HypervisorVm* hv_vm, uint8_t irq, bool asserted) {
if (hv_vm->AssertIrq(irq, asserted)) return;
if (asserted) {
InjectIrq(hv_vm, irq);
}
Expand Down
17 changes: 15 additions & 2 deletions src/core/device/acpi/acpi_pm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
#ifdef _WIN32
#define NOMINMAX
#include <windows.h>
#else
#elif defined(__APPLE__)
#include <mach/mach_time.h>
#include <unistd.h>
#else
#include <time.h>
#include <unistd.h>
#endif

AcpiPm::AcpiPm() {
Expand All @@ -26,7 +29,7 @@ AcpiPm::AcpiPm() {

double elapsed = static_cast<double>(qpc_end.QuadPart - qpc_start.QuadPart)
/ qpf.QuadPart;
#else
#elif defined(__APPLE__)
mach_timebase_info_data_t tb;
mach_timebase_info(&tb);
uint64_t mach_start = mach_absolute_time();
Expand All @@ -36,6 +39,16 @@ AcpiPm::AcpiPm() {
uint64_t mach_end = mach_absolute_time();

double elapsed = static_cast<double>(mach_end - mach_start) * tb.numer / tb.denom / 1e9;
#else
struct timespec ts_start, ts_end;
clock_gettime(CLOCK_MONOTONIC, &ts_start);
uint64_t tsc_start = __rdtsc();
usleep(50000);
uint64_t tsc_end = __rdtsc();
clock_gettime(CLOCK_MONOTONIC, &ts_end);

double elapsed = static_cast<double>(ts_end.tv_sec - ts_start.tv_sec) +
static_cast<double>(ts_end.tv_nsec - ts_start.tv_nsec) / 1e9;
#endif
tsc_freq_ = static_cast<uint64_t>((tsc_end - tsc_start) / elapsed);
tsc_base_ = __rdtsc();
Expand Down
16 changes: 16 additions & 0 deletions src/core/device/serial/uart_16550.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ void Uart16550::PioRead(uint16_t offset, uint8_t size, uint32_t* value) {
} else {
val = 0x01; // No interrupt pending
}
// Bits 7:6 = 11 when FIFO is enabled: 16550A identification signal.
// Linux 8250 autoconfig uses (IIR & 0xC0) == 0xC0 to detect 16550A.
if (fifo_enabled_) {
val |= 0xC0;
}
break;
case kLCR:
val = lcr_;
Expand Down Expand Up @@ -109,6 +114,17 @@ void Uart16550::PioWrite(uint16_t offset, uint8_t size, uint32_t value) {
break;
}
case kFCR:
// FCR bit 0: FIFO enable. Bit 1: clear RX FIFO. Bit 2: clear TX
// FIFO (TX drains synchronously through tx_callback_, so nothing to
// clear). Bits 6:7 set the RX trigger level; we don't model the
// threshold separately since RX is a ring consumed on demand.
fifo_enabled_ = (val & 0x01) != 0;
if (val & 0x02) {
std::lock_guard<std::mutex> lock(rx_mutex_);
rx_head_ = 0;
rx_tail_ = 0;
rx_count_ = 0;
}
break;
case kLCR:
lcr_ = val;
Expand Down
4 changes: 4 additions & 0 deletions src/core/device/serial/uart_16550.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class Uart16550 : public Device {
uint8_t dll_ = 0;
uint8_t dlh_ = 0;
bool thre_pending_ = false;
// Guest has enabled the 16550A FIFO via FCR bit 0. Reported back in
// IIR[7:6] so Linux's 8250 driver identifies the UART as 16550A and
// switches its TX path to burst up to 16 bytes per THRE IRQ roundtrip.
bool fifo_enabled_ = false;

IrqCallback irq_callback_;
TxCallback tx_callback_;
Expand Down
Loading
Loading