Implement initial Hyper-V support on x86 platforms

Haiku currently does not have any form of Hyper-V guest support, and
this change begins work toward #16664.

This change implements initial VMBus bus/device structure and functions.

Change-Id: I2d081f55e99da479d9bc5084f1815ade6cc77fe2
Reviewed-on: https://review.haiku-os.org/c/haiku/+/10333
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: Jérôme Duval <[email protected]>
Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
John Davis
2026-02-28 10:00:04 +00:00
committed by Jérôme Duval
parent b097619769
commit 0c6100feb2
21 changed files with 3487 additions and 0 deletions
+1
View File
@@ -363,6 +363,7 @@ SYSTEM_ADD_ONS_BUS_MANAGERS = [ FFilterByBuildFeatures
acpi@x86,x86_64,arm64
agp_gart@x86,x86_64
ata
hyperv@x86,x86_64
i2c@x86,x86_64
isa@x86,x86_64
mmc
+1
View File
@@ -318,6 +318,7 @@ AddBootModuleSymlinksToPackage
dpc
efi_gpt
generic_ide_pci
hyperv@x86,x86_64
ide_isa@x86
isa@x86,x86_64
intel
+1
View File
@@ -319,6 +319,7 @@ AddBootModuleSymlinksToPackage
virtio_pci
virtio_block
virtio_scsi
hyperv@x86,x86_64
efi_gpt
intel
bfs
+63
View File
@@ -0,0 +1,63 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HYPERV_H_
#define _HYPERV_H_
#include <device_manager.h>
#include <KernelExport.h>
#include <hyperv_spec.h>
#define HYPERV_BUS_NAME "hyperv"
// Device attributes for the VMBus device
#define HYPERV_CHANNEL_ID_ITEM "hyperv/channel"
#define HYPERV_DEVICE_TYPE_ITEM "hyperv/type"
#define HYPERV_INSTANCE_ID_ITEM "hyperv/instance"
#define HYPERV_PRETTYNAME_VMBUS "Hyper-V Virtual Machine Bus"
#define HYPERV_PRETTYNAME_VMBUS_DEVICE_FMT "Hyper-V Channel %u"
#define HYPERV_PRETTYNAME_AVMA "Hyper-V Automatic Virtual Machine Activation"
#define HYPERV_PRETTYNAME_BALLOON "Hyper-V Dynamic Memory"
#define HYPERV_PRETTYNAME_DISPLAY "Hyper-V Display"
#define HYPERV_PRETTYNAME_FIBRECHANNEL "Hyper-V Fibre Channel"
#define HYPERV_PRETTYNAME_FILECOPY "Hyper-V File Copy"
#define HYPERV_PRETTYNAME_HEARTBEAT "Hyper-V Heartbeat"
#define HYPERV_PRETTYNAME_IDE "Hyper-V IDE Accelerator"
#define HYPERV_PRETTYNAME_INPUT "Hyper-V Input"
#define HYPERV_PRETTYNAME_KEYBOARD "Hyper-V Keyboard"
#define HYPERV_PRETTYNAME_KVP "Hyper-V Data Exchange"
#define HYPERV_PRETTYNAME_NETWORK "Hyper-V Network Adapter"
#define HYPERV_PRETTYNAME_PCI "Hyper-V PCI Bridge"
#define HYPERV_PRETTYNAME_RDCONTROL "Hyper-V Remote Desktop Control"
#define HYPERV_PRETTYNAME_RDMA "Hyper-V RDMA"
#define HYPERV_PRETTYNAME_RDVIRT "Hyper-V Remote Desktop Virtualization"
#define HYPERV_PRETTYNAME_SCSI "Hyper-V SCSI Adapter"
#define HYPERV_PRETTYNAME_SHUTDOWN "Hyper-V Guest Shutdown"
#define HYPERV_PRETTYNAME_TIMESYNC "Hyper-V Time Synchronization"
#define HYPERV_PRETTYNAME_VSS "Hyper-V Volume Shadow Copy"
typedef void* hyperv_device;
typedef void (*hyperv_device_callback)(void* data);
// Interface between the VMBus device driver, and the VMBus device bus manager
typedef struct hyperv_device_interface {
driver_module_info info;
uint32 (*get_bus_version)(hyperv_device cookie);
status_t (*open)(hyperv_device cookie, uint32 txLength, uint32 rxLength,
hyperv_device_callback callback, void* callbackData);
void (*close)(hyperv_device cookie);
status_t (*write_packet)(hyperv_device cookie, uint16 type, void* buffer,
uint32 length, bool responseRequired, uint64 transactionID);
status_t (*read_packet)(hyperv_device cookie, vmbus_pkt_header* _header,
uint32* _headerLength, void* _buffer, uint32* _length);
} hyperv_device_interface;
#endif // _HYPERV_H_
+95
View File
@@ -0,0 +1,95 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HYPERV_SPEC_H_
#define _HYPERV_SPEC_H_
#include <SupportDefs.h>
#define HV_PAGE_SIZE 4096
#define HV_PAGE_SHIFT 12
#define HV_PAGE_ALIGN(x) (((x) + (HV_PAGE_SIZE - 1)) & ~(HV_PAGE_SIZE - 1))
#define HV_BYTES_TO_PAGES(x) (HV_PAGE_ALIGN(x) >> HV_PAGE_SHIFT)
#define HV_MS_TO_US(x) ((x) * 1000ULL)
#define HV_STATIC_ASSERT(cond, msg) static_assert(cond, msg)
#define MAKE_VMBUS_VERSION(maj, min) ((((maj) << 16) & 0xFFFF0000) | ((min) & 0x0000FFFF))
#define GET_VMBUS_VERSION_MAJOR(ver) (((ver) >> 16) & 0xFFFF)
#define GET_VMBUS_VERSION_MINOR(ver) ((ver) & 0xFFFF)
#define VMBUS_VERSION_WS2008 MAKE_VMBUS_VERSION(0, 13)
#define VMBUS_VERSION_WS2008R2 MAKE_VMBUS_VERSION(1, 1)
#define VMBUS_VERSION_WIN8_WS2012 MAKE_VMBUS_VERSION(2, 4)
#define VMBUS_VERSION_WIN81_WS2012R2 MAKE_VMBUS_VERSION(3, 0)
#define VMBUS_VERSION_WIN10_RS1_WS2016 MAKE_VMBUS_VERSION(4, 0)
#define VMBUS_VERSION_WIN10_RS3 MAKE_VMBUS_VERSION(4, 1)
#define VMBUS_VERSION_WIN10_V5 MAKE_VMBUS_VERSION(5, 0)
#define VMBUS_VERSION_WIN10_RS4 MAKE_VMBUS_VERSION(5, 1)
#define VMBUS_VERSION_WIN10_RS5_WS2019 MAKE_VMBUS_VERSION(5, 2)
#define VMBUS_VERSION_WS2022 MAKE_VMBUS_VERSION(5, 3)
// VMBus device types
#define VMBUS_TYPE_AVMA "3375baf4-9e15-4b30-b765-67acb10d607b"
#define VMBUS_TYPE_BALLOON "525074dc-8985-46e2-8057-a307dc18a502"
#define VMBUS_TYPE_DISPLAY "da0a7802-e377-4aac-8e77-0558eb1073f8"
#define VMBUS_TYPE_FIBRECHANNEL "2f9bcc4a-0069-4af3-b76b-6fd0be528cda"
#define VMBUS_TYPE_FILECOPY "34d14be3-dee4-41c8-9ae7-6b174977c192"
#define VMBUS_TYPE_HEARTBEAT "57164f39-9115-4e78-ab55-382f3bd5422d"
#define VMBUS_TYPE_IDE "32412632-86cb-44a2-9b5c-50d1417354f5"
#define VMBUS_TYPE_INPUT "cfa8b69e-5b4a-4cc0-b98b-8ba1a1f3f95a"
#define VMBUS_TYPE_KEYBOARD "f912ad6d-2b17-48ea-bd65-f927a61c7684"
#define VMBUS_TYPE_KVP "a9a0f4e7-5a45-4d96-b827-8a841e8c03e6"
#define VMBUS_TYPE_NETWORK "f8615163-df3e-46c5-913f-f2d2f965ed0e"
#define VMBUS_TYPE_PCI "44c4f61d-4444-4400-9d52-802e27ede19f"
#define VMBUS_TYPE_RDCONTROL "f8e65716-3cb3-4a06-9a60-1889c5cccab5"
#define VMBUS_TYPE_RDMA "8c2eaf3d-32a7-4b09-ab99-bd1f1c86b501"
#define VMBUS_TYPE_RDVIRT "276aacf4-ac15-426c-98dd-7521ad3f01fe"
#define VMBUS_TYPE_SCSI "ba6163d9-04a1-4d29-b605-72e2ffb1dc7f"
#define VMBUS_TYPE_SHUTDOWN "0e0b6031-5213-4934-818b-38d90ced39db"
#define VMBUS_TYPE_TIMESYNC "9527e630-d0ae-497b-adce-e80ab0175caf"
#define VMBUS_TYPE_VSS "35fa2e29-ea23-4236-96ae-3a6ebacba440"
// VMBus packet types
enum {
VMBUS_PKTTYPE_INVALID = 0,
VMBUS_PKTTYPE_SYNCH = 1,
VMBUS_PKTTYPE_ADD_TRANSFER_PAGESET = 2,
VMBUS_PKTTYPE_REMOVE_TRANSFER_PAGESET = 3,
VMBUS_PKTTYPE_CREATE_GPADL = 4,
VMBUS_PKTTYPE_FREE_GPADL = 5,
VMBUS_PKTTYPE_DATA_INBAND = 6,
VMBUS_PKTTYPE_DATA_USING_TRANSFER_PAGES = 7,
VMBUS_PKTTYPE_DATA_USING_GPADL = 8,
VMBUS_PKTTYPE_DATA_USING_GPA_DIRECT = 9,
VMBUS_PKTTYPE_CANCEL_REQUEST = 10,
VMBUS_PKTTYPE_COMPLETION = 11,
VMBUS_PKTTYPE_DATA_USING_ADDT_PKT = 12,
VMBUS_PKTTYPE_ADDT_DATA = 13,
VMBUS_PKTTYPE_MAX
};
#define VMBUS_PKT_FLAGS_RESPONSE_REQUIRED (1 << 0)
#define VMBUS_PKT_SIZE_SHIFT 3
#define VMBUS_PKT_ALIGN(x) (((x) + (sizeof(uint64) - 1)) &~ (sizeof(uint64) - 1))
// VMBus packet header
typedef struct {
uint16 type;
uint16 header_length;
uint16 total_length;
uint16 flags;
uint64 transaction_id;
} vmbus_pkt_header;
#endif // _HYPERV_SPEC_H_
+1
View File
@@ -5,6 +5,7 @@ SubInclude HAIKU_TOP src add-ons kernel bus_managers agp_gart ;
SubInclude HAIKU_TOP src add-ons kernel bus_managers ata ;
SubInclude HAIKU_TOP src add-ons kernel bus_managers fdt ;
SubInclude HAIKU_TOP src add-ons kernel bus_managers firewire ;
SubInclude HAIKU_TOP src add-ons kernel bus_managers hyperv ;
SubInclude HAIKU_TOP src add-ons kernel bus_managers i2c ;
SubInclude HAIKU_TOP src add-ons kernel bus_managers isa ;
SubInclude HAIKU_TOP src add-ons kernel bus_managers mmc ;
@@ -0,0 +1,30 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include <new>
#include <stdio.h>
#include <string.h>
#include "Driver.h"
device_manager_info* gDeviceManager;
acpi_module_info* gACPI;
dpc_module_info* gDPC;
module_dependency module_dependencies[] = {
{ B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager },
{ B_ACPI_MODULE_NAME, (module_info**)&gACPI },
{ B_DPC_MODULE_NAME, (module_info **)&gDPC },
{}
};
module_info* modules[] = {
(module_info*)&gVMBusModule,
(module_info*)&gVMBusDeviceModule,
NULL
};
@@ -0,0 +1,49 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HYPERV_DRIVER_H_
#define _HYPERV_DRIVER_H_
#include <new>
#include <stdio.h>
#include <ACPI.h>
#include <device_manager.h>
#include <dpc.h>
#include <KernelExport.h>
#include <hyperv.h>
#define HYPERV_VMBUS_MODULE_NAME "bus_managers/hyperv/root/driver_v1"
#define HYPERV_DEVICE_MODULE_NAME "bus_managers/hyperv/device/v1"
typedef void* hyperv_bus;
typedef void (*hyperv_bus_callback)(void* data);
// Interface between the VMBus bus device driver, and the VMBus bus manager
typedef struct hyperv_bus_interface {
driver_module_info info;
uint32 (*get_version)(hyperv_bus cookie);
status_t (*open_channel)(hyperv_bus cookie, uint32 channel, uint32 gpadl,
uint32 rxOffset, hyperv_bus_callback callback, void* callbackData);
status_t (*close_channel)(hyperv_bus cookie, uint32 channel);
status_t (*allocate_gpadl)(hyperv_bus cookie, uint32 channel, uint32 length,
void** _buffer, uint32* _gpadl);
status_t (*free_gpadl)(hyperv_bus cookie, uint32 channel, uint32 gpadl);
status_t (*signal_channel)(hyperv_bus cookie, uint32 channel);
} hyperv_bus_interface;
extern device_manager_info* gDeviceManager;
extern acpi_module_info* gACPI;
extern dpc_module_info* gDPC;
extern hyperv_bus_interface gVMBusModule;
extern hyperv_device_interface gVMBusDeviceModule;
#endif // _HYPERV_DRIVER_H_
@@ -0,0 +1,23 @@
SubDir HAIKU_TOP src add-ons kernel bus_managers hyperv ;
UsePrivateKernelHeaders ;
UsePrivateHeaders hyperv ;
SubDirHdrs $(HAIKU_TOP) src system kernel device_manager ;
SubDirHdrs $(HAIKU_TOP) src add-ons kernel bus_managers acpi acpica include ;
SubDirHdrs $(HAIKU_TOP) src add-ons kernel bus_managers acpi acpica include platform ;
KernelAddon hyperv :
Driver.cpp
VMBus.cpp
VMBusRequest.cpp
VMBusModule.cpp
VMBusDevice.cpp
VMBusDeviceModule.cpp
: hyperv_arch_bus_manager.a
;
SubInclude HAIKU_TOP src add-ons kernel bus_managers hyperv arch
$(TARGET_KERNEL_ARCH_DIR) ;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,399 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "VMBusDevicePrivate.h"
VMBusDevice::VMBusDevice(device_node* node)
:
fNode(node),
fStatus(B_NO_INIT),
fChannelID(0),
fDPCHandle(NULL),
fIsOpen(false),
fRingGPADL(0),
fRingBuffer(NULL),
fRingBufferLength(0),
fTXRing(NULL),
fTXRingLength(0),
fRXRing(NULL),
fRXRingLength(0),
fCallback(NULL),
fCallbackData(NULL),
fVMBus(NULL),
fVMBusCookie(NULL)
{
CALLED();
fStatus = gDeviceManager->get_attr_uint32(fNode, HYPERV_CHANNEL_ID_ITEM, &fChannelID, false);
if (fStatus != B_OK) {
ERROR("Failed to get channel ID\n");
return;
}
device_node* parent = gDeviceManager->get_parent_node(node);
gDeviceManager->get_driver(parent, (driver_module_info**)&fVMBus, (void**)&fVMBusCookie);
gDeviceManager->put_node(parent);
mutex_init(&fLock, "vmbus device lock");
B_INITIALIZE_SPINLOCK(&fTXLock);
B_INITIALIZE_SPINLOCK(&fRXLock);
}
VMBusDevice::~VMBusDevice()
{
CALLED();
mutex_destroy(&fLock);
if (fDPCHandle != NULL)
gDPC->delete_dpc_queue(fDPCHandle);
}
uint32
VMBusDevice::GetBusVersion()
{
return fVMBus->get_version(fVMBusCookie);
}
status_t
VMBusDevice::Open(uint32 txLength, uint32 rxLength, hyperv_device_callback callback,
void* callbackData)
{
// Ring lengths must be page-aligned.
if (txLength == 0 || rxLength == 0 || txLength != HV_PAGE_ALIGN(txLength)
|| rxLength != HV_PAGE_ALIGN(rxLength))
return B_BAD_VALUE;
MutexLocker locker(fLock);
if (fIsOpen)
return B_BUSY;
uint32 txTotalLength = sizeof(vmbus_ring_buffer) + txLength;
uint32 rxTotalLength = sizeof(vmbus_ring_buffer) + rxLength;
uint32 fRingBufferLength = txTotalLength + rxTotalLength;
TRACE("Open channel %u tx length 0x%X rx length 0x%X\n", fChannelID, txLength, rxLength);
// Create the GPADL used for the ring buffers
status_t status = fVMBus->allocate_gpadl(fVMBusCookie, fChannelID, fRingBufferLength,
&fRingBuffer, &fRingGPADL);
if (status != B_OK) {
ERROR("Failed to allocate GPADL while opening channel %u (%s)\n", fChannelID,
strerror(status));
return status;
}
memset(fRingBuffer, 0, fRingBufferLength);
fTXRing = reinterpret_cast<vmbus_ring_buffer*>(fRingBuffer);
fTXRingLength = txLength;
fRXRing = reinterpret_cast<vmbus_ring_buffer*>(static_cast<uint8*>(fRingBuffer)
+ txTotalLength);
fRXRingLength = rxLength;
// Callback must be in place prior to channel being opened, some devices will begin
// to receive data immediately afterwards
fCallback = callback;
fCallbackData = callbackData;
if (fCallback != NULL) {
status = gDPC->new_dpc_queue(&fDPCHandle, "hyperv vmbus device", B_NORMAL_PRIORITY);
if (status != B_OK)
return status;
}
status = fVMBus->open_channel(fVMBusCookie, fChannelID, fRingGPADL, txTotalLength,
(hyperv_device_callback)((fCallback != NULL) ? _CallbackHandler : NULL),
(fCallback != NULL) ? this : NULL);
if (status != B_OK) {
ERROR("Failed to open channel %u (%s)\n", fChannelID, strerror(status));
return status;
}
fIsOpen = true;
return B_OK;
}
void
VMBusDevice::Close()
{
MutexLocker locker(fLock);
if (!fIsOpen)
return;
fIsOpen = false;
status_t status = fVMBus->close_channel(fVMBusCookie, fChannelID);
if (status != B_OK)
ERROR("Failed to close channel %u (%s)\n", fChannelID, strerror(status));
status = fVMBus->free_gpadl(fVMBusCookie, fChannelID, fRingGPADL);
if (status != B_OK)
ERROR("Failed to free ring GPADL for channel %u (%s)\n", fChannelID, strerror(status));
if (fDPCHandle != NULL) {
gDPC->delete_dpc_queue(fDPCHandle);
fDPCHandle = NULL;
}
}
status_t
VMBusDevice::WritePacket(uint16 type, const void* buffer, uint32 length, bool responseRequired,
uint64 transactionID)
{
TRACE_TX("Channel %u TX pkt %u len 0x%X resp %u tran %lu\n", fChannelID, type, length,
responseRequired, transactionID);
vmbus_pkt_header header;
uint32 totalLength = sizeof(header) + length;
uint32 totalLengthAligned = VMBUS_PKT_ALIGN(totalLength);
header.type = type;
header.header_length = static_cast<uint16>(sizeof(header) >> VMBUS_PKT_SIZE_SHIFT);
header.total_length = static_cast<uint16>(totalLengthAligned >> VMBUS_PKT_SIZE_SHIFT);
header.flags = responseRequired ? VMBUS_PKT_FLAGS_RESPONSE_REQUIRED : 0;
header.transaction_id = transactionID;
iovec pkt[3];
uint64 padding = 0;
pkt[0].iov_base = &header;
pkt[0].iov_len = sizeof(header);
pkt[1].iov_base = (void*)buffer;
pkt[1].iov_len = length;
pkt[2].iov_base = &padding;
pkt[2].iov_len = totalLengthAligned - totalLength;
return _WriteTXData(pkt, 3);
}
status_t
VMBusDevice::PeekPacket(void* _buffer, uint32 length)
{
InterruptsSpinLocker locker(fRXLock);
// Ensure at least the requested amount of data is present, plus the shifted packet index
if (_AvailableRX() < length + sizeof(uint64))
return B_DEV_NOT_READY;
uint32 readIndex = atomic_get((int32*)&fRXRing->read_index);
TRACE_TX("Channel %u RX peek read idx 0x%X write idx 0x%X\n", fChannelID, readIndex,
atomic_get((int32*)&fRXRing->write_index));
_ReadRX(readIndex, _buffer, length);
return B_OK;
}
status_t
VMBusDevice::ReadPacket(vmbus_pkt_header* _header, uint32* _headerLength, void* _buffer,
uint32* _length)
{
vmbus_pkt_header header;
vmbus_pkt_header* headerPtr;
if (_header != NULL) {
if (_headerLength == NULL || *_headerLength < sizeof(vmbus_pkt_header))
return B_BAD_VALUE;
headerPtr = _header;
} else {
headerPtr = &header;
}
status_t status = PeekPacket(headerPtr, sizeof(vmbus_pkt_header));
if (status != B_OK)
return status;
uint32 headerLength = headerPtr->header_length << VMBUS_PKT_SIZE_SHIFT;
uint32 totalLength = headerPtr->total_length << VMBUS_PKT_SIZE_SHIFT;
if (headerLength < sizeof(vmbus_pkt_header) || totalLength < headerLength) {
ERROR("Channel %u RX invalid pkt hdr len 0x%X tot len 0x%X\n", fChannelID, headerLength,
totalLength);
return B_IO_ERROR;
}
uint32 dataLength = totalLength - headerLength;
TRACE_RX("Channel %u RX pkt %u hdr len 0x%X tot len 0x%X\n", fChannelID, headerPtr->type,
headerLength, totalLength);
// Ensure provided buffers are large enough
if (_header != NULL) {
if (*_headerLength < headerLength) {
*_headerLength = headerLength;
return B_NO_MEMORY;
}
*_headerLength = headerLength;
}
if (*_length < dataLength) {
*_length = dataLength;
return B_NO_MEMORY;
}
*_length = dataLength;
InterruptsSpinLocker locker(fRXLock);
if (_AvailableRX() < totalLength + sizeof(uint64))
return B_DEV_NOT_READY;
uint32 readIndexNew = atomic_get((int32*)&fRXRing->read_index);
TRACE_TX("Channel %u RX old read idx 0x%X write idx 0x%X\n", fChannelID, readIndexNew,
atomic_get((int32*)&fRXRing->write_index));
// Read the header, data, and seek past the shifted read index
if (_header != NULL && headerLength > sizeof(vmbus_pkt_header))
readIndexNew = _ReadRX(readIndexNew, _header, headerLength);
else
readIndexNew = _SeekRX(readIndexNew, headerLength);
readIndexNew = _ReadRX(readIndexNew, _buffer, dataLength);
readIndexNew = _SeekRX(readIndexNew, sizeof(uint64));
memory_write_barrier();
atomic_set((int32*)&fRXRing->read_index, (int32)readIndexNew);
TRACE_TX("Channel %u RX new read idx 0x%X write idx 0x%X\n", fChannelID,
atomic_get((int32*)&fRXRing->read_index), atomic_get((int32*)&fRXRing->write_index));
return B_OK;
}
/*static*/ void
VMBusDevice::_CallbackHandler(void* arg)
{
VMBusDevice* vmbusDevice = reinterpret_cast<VMBusDevice*>(arg);
gDPC->queue_dpc(vmbusDevice->fDPCHandle, _DPCHandler, arg);
}
/*static*/ void
VMBusDevice::_DPCHandler(void* arg)
{
VMBusDevice* vmbusDevice = reinterpret_cast<VMBusDevice*>(arg);
vmbusDevice->fCallback(vmbusDevice->fCallbackData);
}
inline uint32
VMBusDevice::_AvailableTX()
{
uint32 readIndex = atomic_get((int32*)&fTXRing->read_index);
uint32 writeIndex = atomic_get((int32*)&fTXRing->write_index);
return (writeIndex >= readIndex)
? (fTXRingLength - (writeIndex - readIndex))
: (readIndex - writeIndex);
}
inline uint32
VMBusDevice::_WriteTX(uint32 writeIndex, const void* buffer, uint32 length)
{
// Copy data to the TX ring, accounting for wraparound if needed
if (length > fTXRingLength - writeIndex) {
uint32 fragmentLength = fTXRingLength - writeIndex;
TRACE("Channel %u TX wraparound by %u bytes\n", fChannelID, fragmentLength);
memcpy(&fTXRing->buffer[writeIndex], buffer, fragmentLength);
memcpy(&fTXRing->buffer[0], static_cast<const uint8*>(buffer) + fragmentLength,
length - fragmentLength);
} else {
memcpy(&fTXRing->buffer[writeIndex], buffer, length);
}
// Advance the write index accounting for the wraparound
return (writeIndex + length) % fTXRingLength;
}
status_t
VMBusDevice::_WriteTXData(const iovec txData[], size_t txDataCount)
{
uint64 writeIndexOldShifted;
uint32 length = sizeof(writeIndexOldShifted);
for (uint32 i = 0; i < txDataCount; i++)
length += txData[i].iov_len;
InterruptsSpinLocker locker(fTXLock);
// Ensure there is enough space in the TX ring; the write index
// cannot equal the read index as this normally indicates there is no data in the ring
if (length > _AvailableTX())
return B_DEV_NOT_READY;
uint32 writeIndexOld = atomic_get((int32*)&fTXRing->write_index);
TRACE_TX("Channel %u TX old write idx 0x%X read idx 0x%X\n", fChannelID, writeIndexOld,
atomic_get((int32*)&fTXRing->read_index));
// Copy the data to the TX ring
uint32 writeIndexNew = writeIndexOld;
for (uint32 i = 0; i < txDataCount; i++)
writeIndexNew = _WriteTX(writeIndexNew, txData[i].iov_base, txData[i].iov_len);
// Write the old write index immediately after the newly written data, shifted over by 32 bits
writeIndexOldShifted = static_cast<uint64>(writeIndexOld) << 32;
writeIndexNew = _WriteTX(writeIndexNew, &writeIndexOldShifted, sizeof(writeIndexOldShifted));
memory_write_barrier();
atomic_set((int32*)&fTXRing->write_index, (int32)writeIndexNew);
TRACE_TX("Channel %u TX new write idx 0x%X read idx 0x%X\n", fChannelID,
atomic_get((int32*)&fTXRing->write_index), atomic_get((int32*)&fTXRing->read_index));
locker.Unlock();
// Signal Hyper-V if required; signaling is only needed if the ring buffer is changing state
// from empty to having some amount of data. It does not need a signal if the buffer already
// has some amount of data, and we are just adding more
memory_read_barrier();
if (fTXRing->interrupt_mask == 0
&& writeIndexOld == (uint32)atomic_get((int32*)&fTXRing->read_index)) {
atomic_add64((int64*)&fTXRing->guest_to_host_interrupt_count, 1);
fVMBus->signal_channel(fVMBusCookie, fChannelID);
}
return B_OK;
}
inline uint32
VMBusDevice::_AvailableRX()
{
uint32 readIndex = atomic_get((int32*)&fRXRing->read_index);
uint32 writeIndex = atomic_get((int32*)&fRXRing->write_index);
return fRXRingLength - ((writeIndex >= readIndex)
? (fRXRingLength - (writeIndex - readIndex))
: (readIndex - writeIndex));
}
inline uint32
VMBusDevice::_SeekRX(uint32 readIndex, uint32 length)
{
// Advance the read index accounting for the wraparound
return (readIndex + length) % fRXRingLength;
}
inline uint32
VMBusDevice::_ReadRX(uint32 readIndex, void* buffer, uint32 length)
{
// Copy data from the RX ring, accounting for wraparound if needed
if (length > fRXRingLength - readIndex) {
uint32 fragmentLength = fRXRingLength - readIndex;
TRACE("Channel %u RX wraparound by %u bytes\n", fChannelID, fragmentLength);
memcpy(buffer, &fRXRing->buffer[readIndex], fragmentLength);
memcpy(static_cast<uint8*>(buffer) + fragmentLength, &fRXRing->buffer[0],
length - fragmentLength);
} else {
memcpy(buffer, &fRXRing->buffer[readIndex], length);
}
return _SeekRX(readIndex, length);
}
@@ -0,0 +1,133 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "VMBusDevicePrivate.h"
static status_t
vmbus_device_init(device_node* node, void** _driverCookie)
{
CALLED();
VMBusDevice* device = new(std::nothrow) VMBusDevice(node);
if (device == NULL) {
ERROR("Unable to allocate VMBus device object\n");
return B_NO_MEMORY;
}
status_t status = device->InitCheck();
if (status != B_OK) {
ERROR("Failed to set up VMBus device object\n");
delete device;
return status;
}
TRACE("VMBus device object created\n");
*_driverCookie = device;
return B_OK;
}
static void
vmbus_device_uninit(void* driverCookie)
{
CALLED();
VMBusDevice* device = reinterpret_cast<VMBusDevice*>(driverCookie);
delete device;
}
static void
vmbus_device_removed(void* _device)
{
CALLED();
}
static uint32
vmbus_device_get_bus_version(hyperv_device cookie)
{
CALLED();
VMBusDevice* device = reinterpret_cast<VMBusDevice*>(cookie);
return device->GetBusVersion();
}
static status_t
vmbus_device_open(hyperv_device cookie, uint32 txLength, uint32 rxLength,
hyperv_device_callback callback, void* callbackData)
{
CALLED();
VMBusDevice* device = reinterpret_cast<VMBusDevice*>(cookie);
return device->Open(txLength, rxLength, callback, callbackData);
}
static void
vmbus_device_close(hyperv_device cookie)
{
CALLED();
VMBusDevice* device = reinterpret_cast<VMBusDevice*>(cookie);
device->Close();
}
static status_t
vmbus_device_write_packet(hyperv_device cookie, uint16 type, void* buffer, uint32 length,
bool responseRequired, uint64 transactionID)
{
VMBusDevice* device = reinterpret_cast<VMBusDevice*>(cookie);
return device->WritePacket(type, buffer, length, responseRequired, transactionID);
}
static status_t
vmbus_device_read_packet(hyperv_device cookie, vmbus_pkt_header* _header, uint32* _headerLength,
void* _buffer, uint32* _length)
{
VMBusDevice* device = reinterpret_cast<VMBusDevice*>(cookie);
return device->ReadPacket(_header, _headerLength, _buffer, _length);
}
static status_t
std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
case B_MODULE_UNINIT:
return B_OK;
default:
break;
}
return B_ERROR;
}
hyperv_device_interface gVMBusDeviceModule = {
{
{
HYPERV_DEVICE_MODULE_NAME,
0,
std_ops
},
NULL, // supported devices
NULL, // register node
vmbus_device_init,
vmbus_device_uninit,
NULL, // register child devices
NULL, // rescan
vmbus_device_removed
},
vmbus_device_get_bus_version,
vmbus_device_open,
vmbus_device_close,
vmbus_device_write_packet,
vmbus_device_read_packet
};
@@ -0,0 +1,97 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _VMBUS_DEVICE_PRIVATE_H_
#define _VMBUS_DEVICE_PRIVATE_H_
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <lock.h>
#include <util/AutoLock.h>
#include <hyperv.h>
#include "Driver.h"
#include "hyperv_spec_private.h"
//#define TRACE_VMBUS_DEVICE
#ifdef TRACE_VMBUS_DEVICE
# define TRACE(x...) dprintf("\33[36mvmbus_device:\33[0m " x)
#else
# define TRACE(x...) ;
#endif
//#define TRACE_VMBUS_DEVICE_TX
#ifdef TRACE_VMBUS_DEVICE_TX
# define TRACE_TX(x...) dprintf("\33[36mvmbus_device:\33[0m " x)
#else
# define TRACE_TX(x...) ;
#endif
//#define TRACE_VMBUS_DEVICE_RX
#ifdef TRACE_VMBUS_DEVICE_RX
# define TRACE_RX(x...) dprintf("\33[36mvmbus_device:\33[0m " x)
#else
# define TRACE_RX(x...) ;
#endif
#define TRACE_ALWAYS(x...) dprintf("\33[36mvmbus_device:\33[0m " x)
#define ERROR(x...) dprintf("\33[36mvmbus_device:\33[0m " x)
#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
class VMBusDevice {
public:
VMBusDevice(device_node* node);
~VMBusDevice();
status_t InitCheck() const { return fStatus; }
uint32 GetBusVersion();
status_t Open(uint32 txLength, uint32 rxLength,
hyperv_device_callback callback, void* callbackData);
void Close();
status_t WritePacket(uint16 type, const void* buffer, uint32 length,
bool responseRequired, uint64 transactionID);
status_t PeekPacket(void* _buffer, uint32 length);
status_t ReadPacket(vmbus_pkt_header* _header, uint32* _headerLength,
void* _buffer, uint32* _length);
private:
static void _CallbackHandler(void* arg);
static void _DPCHandler(void* arg);
inline uint32 _AvailableTX();
inline uint32 _WriteTX(uint32 writeIndex, const void* buffer, uint32 length);
status_t _WriteTXData(const iovec txData[], size_t txDataCount);
inline uint32 _AvailableRX();
inline uint32 _SeekRX(uint32 readIndex, uint32 length);
inline uint32 _ReadRX(uint32 readIndex, void* buffer, uint32 length);
private:
device_node* fNode;
status_t fStatus;
uint32 fChannelID;
mutex fLock;
void* fDPCHandle;
bool fIsOpen;
uint32 fRingGPADL;
void* fRingBuffer;
uint32 fRingBufferLength;
vmbus_ring_buffer* fTXRing;
uint32 fTXRingLength;
vmbus_ring_buffer* fRXRing;
uint32 fRXRingLength;
spinlock fTXLock;
spinlock fRXLock;
hyperv_device_callback fCallback;
void* fCallbackData;
hyperv_bus_interface* fVMBus;
hyperv_bus fVMBusCookie;
};
#endif // _VMBUS_DEVICE_PRIVATE_H_
@@ -0,0 +1,184 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "VMBusPrivate.h"
static float
vmbus_supports_device(device_node* parent)
{
CALLED();
const char* bus;
// Check if the parent is the root node
if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false) != B_OK) {
TRACE("Could not find required attribute device/bus\n");
return -1;
}
if (strcmp(bus, "root") != 0)
return 0.0f;
status_t status = vmbus_detect_hyperv();
if (status != B_OK)
return 0.0f;
return 0.8f;
}
static status_t
vmbus_register_device(device_node* parent)
{
CALLED();
device_attr attributes[] = {
{ B_DEVICE_BUS, B_STRING_TYPE,
{ .string = HYPERV_BUS_NAME }},
{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE,
{ .string = HYPERV_PRETTYNAME_VMBUS }},
{ NULL }
};
return gDeviceManager->register_node(parent, HYPERV_VMBUS_MODULE_NAME, attributes, NULL, NULL);
}
static status_t
vmbus_init_driver(device_node* node, void** _driverCookie)
{
CALLED();
VMBus* vmbus = new(std::nothrow) VMBus(node);
if (vmbus == NULL) {
ERROR("Unable to allocate VMBus object\n");
return B_NO_MEMORY;
}
status_t status = vmbus->InitCheck();
if (status != B_OK) {
ERROR("Failed to set up VMBus object\n");
delete vmbus;
return status;
}
TRACE("VMBus object created\n");
*_driverCookie = vmbus;
return B_OK;
}
static void
vmbus_uninit_driver(void* driverCookie)
{
CALLED();
VMBus* vmbus = reinterpret_cast<VMBus*>(driverCookie);
delete vmbus;
}
static status_t
vmbus_register_child_devices(void* driverCookie)
{
CALLED();
VMBus* vmbus = reinterpret_cast<VMBus*>(driverCookie);
return vmbus->RequestChannels();
}
static uint32
vmbus_get_version(hyperv_bus cookie)
{
CALLED();
VMBus* vmbus = reinterpret_cast<VMBus*>(cookie);
return vmbus->GetVersion();
}
static status_t
vmbus_open_channel(hyperv_bus cookie, uint32 channel, uint32 gpadl, uint32 rxOffset,
hyperv_bus_callback callback, void* callbackData)
{
CALLED();
VMBus* vmbus = reinterpret_cast<VMBus*>(cookie);
return vmbus->OpenChannel(channel, gpadl, rxOffset, callback, callbackData);
}
static status_t
vmbus_close_channel(hyperv_bus cookie, uint32 channel)
{
CALLED();
VMBus* vmbus = reinterpret_cast<VMBus*>(cookie);
return vmbus->CloseChannel(channel);
}
static status_t
vmbus_allocate_gpadl(hyperv_bus cookie, uint32 channel, uint32 length, void** _buffer,
uint32* _gpadl)
{
CALLED();
VMBus* vmbus = reinterpret_cast<VMBus*>(cookie);
return vmbus->AllocateGPADL(channel, length, _buffer, _gpadl);
}
static status_t
vmbus_free_gpadl(hyperv_bus cookie, uint32 channel, uint32 gpadl)
{
CALLED();
VMBus* vmbus = reinterpret_cast<VMBus*>(cookie);
return vmbus->FreeGPADL(channel, gpadl);
}
static status_t
vmbus_signal_channel(hyperv_bus cookie, uint32 channel)
{
VMBus* vmbus = reinterpret_cast<VMBus*>(cookie);
return vmbus->SignalChannel(channel);
}
static status_t
std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
case B_MODULE_UNINIT:
return B_OK;
default:
break;
}
return B_ERROR;
}
hyperv_bus_interface gVMBusModule = {
{
{
HYPERV_VMBUS_MODULE_NAME,
0,
std_ops
},
vmbus_supports_device,
vmbus_register_device,
vmbus_init_driver,
vmbus_uninit_driver,
vmbus_register_child_devices,
NULL, // rescan bus
NULL // device removed
},
vmbus_get_version,
vmbus_open_channel,
vmbus_close_channel,
vmbus_allocate_gpadl,
vmbus_free_gpadl,
vmbus_signal_channel
};
@@ -0,0 +1,220 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _VMBUS_PRIVATE_H_
#define _VMBUS_PRIVATE_H_
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ACPI.h>
#include "acpi.h"
#include <AutoDeleter.h>
#include <AutoDeleterOS.h>
#include <condition_variable.h>
#include <cpu.h>
#include <dpc.h>
#include <lock.h>
#include <smp.h>
#include <util/AutoLock.h>
#include <util/DoublyLinkedList.h>
#include <hyperv.h>
#include "Driver.h"
#include "hyperv_spec_private.h"
#include "VMBusRequest.h"
//#define TRACE_VMBUS
#ifdef TRACE_VMBUS
# define TRACE(x...) dprintf("\33[35mvmbus:\33[0m " x)
#else
# define TRACE(x...) ;
#endif
#define TRACE_ALWAYS(x...) dprintf("\33[35mvmbus:\33[0m " x)
#define ERROR(x...) dprintf("\33[35mvmbus:\33[0m " x)
#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
status_t vmbus_detect_hyperv();
class VMBus;
// CPU index to VMBus linkage
typedef struct {
int32_t cpu;
VMBus* vmbus;
} VMBusCPU;
// Channel GPADL info
struct VMBusGPADL : DoublyLinkedListLinkImpl<VMBusGPADL> {
uint32 gpadl_id;
uint32 length;
area_id areaid;
};
typedef DoublyLinkedList<VMBusGPADL> VMBusGPADLList;
// VMBus channel and linked GPADLs
struct VMBusChannel : DoublyLinkedListLinkImpl<VMBusChannel> {
VMBusChannel(uint32 channelID, vmbus_guid_t typeID, vmbus_guid_t instanceID)
:
channel_id(channelID),
type_id(typeID),
instance_id(instanceID),
dedicated_int(false),
connection_id(VMBUS_CONNID_EVENTS),
node(NULL),
callback(NULL),
callback_data(NULL)
{
mutex_init(&lock, "vmbus channel");
}
~VMBusChannel()
{
mutex_lock(&lock);
// Free any stray GPADL buffers
VMBusGPADLList::Iterator iterator = gpadls.GetIterator();
while (iterator.HasNext()) {
VMBusGPADL* gpadl = iterator.Next();
gpadls.Remove(gpadl);
delete_area(gpadl->areaid);
delete gpadl;
}
mutex_destroy(&lock);
}
uint32_t channel_id;
vmbus_guid_t type_id;
vmbus_guid_t instance_id;
bool dedicated_int;
uint32_t connection_id;
mutex lock;
device_node* node;
VMBusGPADLList gpadls;
hyperv_bus_callback callback;
void* callback_data;
};
typedef DoublyLinkedList<VMBusChannel> VMBusChannelList;
typedef void (VMBus::*VMBusEventFlagsHandler)(int32 cpu);
class VMBus {
public:
VMBus(device_node* node);
~VMBus();
status_t InitCheck() const { return fStatus; }
uint32 GetVersion() const { return fVersion; }
status_t RequestChannels();
status_t OpenChannel(uint32 channelID, uint32 gpadlID, uint32 rxOffset,
hyperv_bus_callback callback, void* callbackData);
status_t CloseChannel(uint32 channelID);
status_t AllocateGPADL(uint32 channelID, uint32 length, void** _buffer,
uint32* _gpadlID);
status_t FreeGPADL(uint32 channelID, uint32 gpadlID);
status_t SignalChannel(uint32 channelID);
private:
status_t _EnableHypercalls();
void _DisableHypercalls();
uint16 _HypercallPostMessage(phys_addr_t physAddr);
uint16 _HypercallSignalEvent(uint32 connId);
status_t _EnableInterrupts();
void _DisableInterrupts();
static void _EnableInterruptCPUHandler(void* data, int cpu);
void _EnableInterruptCPU(int32 cpu);
static void _DisableInterruptCPUHandler(void* data, int cpu);
void _DisableInterruptCPU(int32 cpu);
static acpi_status _InterruptACPICallback(ACPI_RESOURCE* res, void* context);
static int32 _InterruptHandler(void* data);
int32 _Interrupt();
void _InterruptEventFlags(int32 cpu);
void _InterruptEventFlagsLegacy(int32 cpu);
void _InterruptEventFlagsNull(int32 cpu);
static void _MessageDPCHandler(void* arg);
void _ProcessPendingMessage(int32_t cpu);
void _SendEndOfMessage(int32_t cpu);
static void _SignalEom(void*, int);
status_t _SendRequest(VMBusRequest* request,
ConditionVariableEntry* waitEntry = NULL,
bool wait = true);
status_t _WaitForRequest(VMBusRequest* request,
ConditionVariableEntry* waitEntry);
void _CancelRequest(VMBusRequest* request);
status_t _ConnectVersion(uint32 version);
status_t _Disconnect();
status_t _Connect();
static status_t _ChannelQueueThreadHandler(void* arg);
status_t _ChannelQueueThread();
VMBusChannel* _GetChannel(uint32 channelID, MutexLocker& channelLocker);
status_t _RegisterChannel(VMBusChannel* channel);
status_t _UnregisterChannel(VMBusChannel* channel);
area_id _AllocateBuffer(const char* name, size_t length,
uint32 protection, void** _buffer, phys_addr_t* _physAddr);
inline uint32 _GetGPADLHandle();
private:
device_node* fNode;
status_t fStatus;
void* fMessageDPCHandle;
VMBusEventFlagsHandler fEventFlagsHandler;
void* fHypercallPage;
area_id fHyperCallArea;
phys_addr_t fHyperCallPhys;
uint8 fIRQ;
uint8 fInterruptVector;
int32 fCPUCount;
VMBusCPU* fCPUData;
volatile hv_message_page* fCPUMessages;
area_id fCPUMessagesArea;
phys_addr_t fCPUMessagesPhys;
hv_event_flags_page* fCPUEventFlags;
area_id fCPUEventFlagsArea;
phys_addr_t fCPUEventFlagsPhys;
bool fConnected;
uint32 fVersion;
uint32 fConnectionId;
vmbus_event_flags_page* fEventFlags;
void* fMonitor1;
void* fMonitor2;
area_id fVMBusDataArea;
phys_addr_t fVMBusDataPhys;
VMBusRequestList fRequestList;
mutex fRequestLock;
int32 fCurrentGPADLHandle;
uint32 fMaxChannelsCount;
uint32 fHighestChannelID;
VMBusChannel** fChannels;
spinlock fChannelsSpinlock;
rw_lock fChannelsLock;
VMBusChannelList fChannelOfferList;
VMBusChannelList fChannelRescindList;
mutex fChannelQueueLock;
sem_id fChannelQueueSem;
thread_id fChannelQueueThread;
};
#endif // VMBUS_PRIVATE_H
@@ -0,0 +1,106 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "VMBusRequest.h"
VMBusRequest::VMBusRequest(uint32 type, uint32 channelID)
: VMBusRequest(type, channelID, 0)
{
}
VMBusRequest::VMBusRequest(uint32 type, uint32 channelID, uint32 length)
:
fStatus(B_NOT_INITIALIZED),
fChannelID(channelID),
fResponseType(VMBUS_MSGTYPE_INVALID),
fResponseData(0),
fMessage(NULL),
fHcPostMessage(NULL),
fHcPostMessageArea(0),
fHcPostMessagePhys(0)
{
if (type >= VMBUS_MSGTYPE_MAX) {
fStatus = B_BAD_VALUE;
return;
}
if (length == 0) {
length = vmbus_msg_lengths[type];
if (length == 0) {
fStatus = B_BAD_VALUE;
return;
}
}
fHcPostMessageArea = create_area("vmbus request", (void**)&fHcPostMessage,
B_ANY_KERNEL_ADDRESS, sizeof(*fHcPostMessage), B_CONTIGUOUS,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
if (fHcPostMessageArea < B_OK) {
fStatus = fHcPostMessageArea;
return;
}
physical_entry entry;
fStatus = get_memory_map(fHcPostMessage, sizeof(*fHcPostMessage), &entry, 1);
if (fStatus != B_OK) {
delete_area(fHcPostMessageArea);
return;
}
fHcPostMessagePhys = entry.address;
fHcPostMessage->connection_id = VMBUS_CONNID_MESSAGE;
fHcPostMessage->reserved = 0;
fHcPostMessage->message_type = HYPERV_MSGTYPE_CHANNEL;
fHcPostMessage->data_size = length;
fMessage = reinterpret_cast<vmbus_msg*>(fHcPostMessage->data);
fMessage->header.type = type;
fMessage->header.reserved = 0;
fConditionVariable.Init(this, "vmbus request");
}
VMBusRequest::~VMBusRequest()
{
delete_area(fHcPostMessageArea);
}
void
VMBusRequest::Add(ConditionVariableEntry* waitEntry)
{
if (fResponseType == VMBUS_MSGTYPE_INVALID)
return;
fConditionVariable.Add(waitEntry);
}
status_t
VMBusRequest::Wait(ConditionVariableEntry* waitEntry)
{
if (fResponseType == VMBUS_MSGTYPE_INVALID)
return B_OK;
return waitEntry->Wait(B_RELATIVE_TIMEOUT | B_CAN_INTERRUPT, VMBUS_TIMEOUT);
}
void
VMBusRequest::Notify(status_t status, vmbus_msg* message, uint32 messageLength)
{
if (fResponseType == VMBUS_MSGTYPE_INVALID)
return;
if (status == B_OK) {
memcpy(fMessage, message, messageLength);
SetLength(messageLength);
}
fConditionVariable.NotifyAll(status);
}
@@ -0,0 +1,60 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _VMBUS_REQUEST_H_
#define _VMBUS_REQUEST_H_
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <condition_variable.h>
#include <util/DoublyLinkedList.h>
#include <hyperv.h>
#include "Driver.h"
#include "hyperv_spec_private.h"
#define VMBUS_TIMEOUT HV_MS_TO_US(20000)
class VMBusRequest : public DoublyLinkedListLinkImpl<VMBusRequest> {
public:
VMBusRequest(uint32 type, uint32 channelID);
VMBusRequest(uint32 type, uint32 channelID, uint32 length);
~VMBusRequest();
status_t InitCheck() const { return fStatus; }
vmbus_msg* GetMessage() const { return fMessage; }
uint32 GetChannelID() const { return fChannelID; }
uint32 GetResponseType() const { return fResponseType; }
void SetResponseType(uint32 type) { fResponseType = type; }
uint32 GetResponseData() const { return fResponseData; }
void SetResponseData(uint32 data) { fResponseData = data; }
uint32 GetLength() const { return fHcPostMessage->data_size; }
void SetLength(uint32 length) { fHcPostMessage->data_size = length; }
phys_addr_t GetHcPostPhys() const { return fHcPostMessagePhys; }
void Add(ConditionVariableEntry* waitEntry);
status_t Wait(ConditionVariableEntry* waitEntry);
void Notify(status_t status, vmbus_msg* message, uint32 messageLength);
private:
status_t fStatus;
uint32 fChannelID;
uint32 fResponseType;
uint32 fResponseData;
vmbus_msg* fMessage;
hypercall_post_msg_input* fHcPostMessage;
area_id fHcPostMessageArea;
phys_addr_t fHcPostMessagePhys;
ConditionVariable fConditionVariable;
};
typedef DoublyLinkedList<VMBusRequest> VMBusRequestList;
#endif // _VMBUS_REQUEST_H_
@@ -0,0 +1,14 @@
SubDir HAIKU_TOP src add-ons kernel bus_managers hyperv arch x86 ;
SubDirHdrs [ FDirName $(SUBDIR) $(DOTDOT) $(DOTDOT) ] ;
UsePrivateKernelHeaders ;
UsePrivateHeaders hyperv [ FDirName kernel arch x86 ] ;
SubDirHdrs $(HAIKU_TOP) src system kernel device_manager ;
SubDirHdrs $(HAIKU_TOP) src add-ons kernel bus_managers acpi acpica include ;
SubDirHdrs $(HAIKU_TOP) src add-ons kernel bus_managers acpi acpica include platform ;
KernelStaticLibrary hyperv_arch_bus_manager :
VMBus_x86.cpp
;
@@ -0,0 +1,198 @@
/*
* Copyright 2026 John Davis
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include <arch_cpu.h>
#include "hyperv_cpu.h"
#include "VMBusPrivate.h"
status_t
vmbus_detect_hyperv()
{
CALLED();
// Check for hypervisor.
cpu_ent* cpu = get_cpu_struct();
if ((cpu->arch.feature[FEATURE_EXT] & IA32_FEATURE_EXT_HYPERVISOR) == 0) {
TRACE("No hypervisor detected\n");
return B_ERROR;
}
// Check for Hyper-V CPUID leaves.
cpuid_info cpuInfo;
get_cpuid(&cpuInfo, IA32_CPUID_LEAF_HYPERVISOR, 0);
if (cpuInfo.regs.eax < IA32_CPUID_LEAF_HV_IMP_LIMITS) {
TRACE("Not running on Hyper-V\n");
return B_ERROR;
}
// Check for Hyper-V signature.
get_cpuid(&cpuInfo, IA32_CPUID_LEAF_HV_INT_ID, 0);
if (cpuInfo.regs.eax != HV_CPUID_INTERFACE_ID) {
TRACE("Not running on Hyper-V\n");
return B_ERROR;
}
#ifdef TRACE_HYPERV
get_cpuid(&cpuInfo, IA32_CPUID_LEAF_HV_SYS_ID, 0);
TRACE("Hyper-V version: %d.%d.%d [SP%d]\n", cpuInfo.regs.ebx >> 16, cpuInfo.regs.ebx & 0xFFFF,
cpuInfo.regs.eax, cpuInfo.regs.ecx);
#endif
return B_OK;
}
status_t
VMBus::_EnableHypercalls()
{
// Guest ID must be set prior to enabling hypercalls
x86_write_msr(IA32_MSR_HV_GUEST_OS_ID, IA32_MSR_HV_GUEST_OS_ID_FREEBSD);
uint64 msr = x86_read_msr(IA32_MSR_HV_HYPERCALL);
msr = ((fHyperCallPhys >> HV_PAGE_SHIFT) << IA32_MSR_HV_HYPERCALL_PAGE_SHIFT)
| (msr & IA32_MSR_HV_HYPERCALL_RSVD_MASK) | IA32_MSR_HV_HYPERCALL_ENABLE;
x86_write_msr(IA32_MSR_HV_HYPERCALL, msr);
// Check that hypercalls are enabled
msr = x86_read_msr(IA32_MSR_HV_HYPERCALL);
if ((msr & IA32_MSR_HV_HYPERCALL_ENABLE) == 0)
return B_ERROR;
TRACE("Hypercalls enabled at %p\n", fHypercallPage);
return B_OK;
}
void
VMBus::_DisableHypercalls()
{
uint64 msr = x86_read_msr(IA32_MSR_HV_HYPERCALL);
msr &= IA32_MSR_HV_HYPERCALL_RSVD_MASK;
x86_write_msr(IA32_MSR_HV_HYPERCALL, msr);
TRACE("Hypercalls disabled\n");
}
uint16
VMBus::_HypercallPostMessage(phys_addr_t physAddr)
{
uint64 status;
#if defined(__i386__)
__asm __volatile("call *%5"
: "=A" (status)
: "d" (0), "a" (HYPERCALL_POST_MESSAGE), "b" (0), "c" (static_cast<uint32>(physAddr)),
"m" (fHypercallPage));
#elif defined(__x86_64__)
__asm __volatile("call *%3"
: "=a" (status)
: "c" (HYPERCALL_POST_MESSAGE), "d" (physAddr), "m" (fHypercallPage));
#endif
return (uint16)(status & 0xFFFF);
}
uint16
VMBus::_HypercallSignalEvent(uint32 connId)
{
uint64 status;
#if defined(__i386__)
__asm __volatile("call *%5"
: "=A" (status)
: "d" (0), "a" (HYPERCALL_SIGNAL_EVENT), "b" (0), "c" (connId), "m" (fHypercallPage));
#elif defined(__x86_64__)
__asm __volatile("call *%3"
: "=a" (status)
: "c" (HYPERCALL_SIGNAL_EVENT), "d" (connId), "m" (fHypercallPage));
#endif
return (uint16)(status & 0xFFFF);
}
void
VMBus::_EnableInterruptCPU(int32 cpu)
{
phys_addr_t messagesPhys = fCPUMessagesPhys + (sizeof(*fCPUMessages) * cpu);
phys_addr_t eventFlagsPhys = fCPUEventFlagsPhys + (sizeof(*fCPUEventFlags) * cpu);
// Configure SIMP and SIEFP
uint64 msr = x86_read_msr(IA32_MSR_HV_SIMP);
msr = ((messagesPhys >> HV_PAGE_SHIFT) << IA32_MSR_HV_SIMP_PAGE_SHIFT)
| (msr & IA32_MSR_HV_SIMP_RSVD_MASK) | IA32_MSR_HV_SIMP_ENABLE;
x86_write_msr(IA32_MSR_HV_SIMP, msr);
TRACE("cpu%u: simp new msr 0x%llX\n", cpu, (unsigned long long)msr);
msr = x86_read_msr(IA32_MSR_HV_SIEFP);
msr = ((eventFlagsPhys >> HV_PAGE_SHIFT) << IA32_MSR_HV_SIEFP_PAGE_SHIFT)
| (msr & IA32_MSR_HV_SIEFP_RSVD_MASK) | IA32_MSR_HV_SIEFP_ENABLE;
x86_write_msr(IA32_MSR_HV_SIEFP, msr);
TRACE("cpu%u: siefp new msr 0x%llX\n", cpu, (unsigned long long)msr);
// Configure interrupt vector for incoming VMBus messages
msr = x86_read_msr(IA32_MSR_HV_SINT0 + VMBUS_SINT_MESSAGE);
msr = fInterruptVector | (msr & IA32_MSR_HV_SINT_RSVD_MASK);
x86_write_msr(IA32_MSR_HV_SINT0 + VMBUS_SINT_MESSAGE, msr);
TRACE("cpu%u: sint%u new msr 0x%llX\n", VMBUS_SINT_MESSAGE, cpu, (unsigned long long)msr);
// Configure interrupt vector for VMBus timers
msr = x86_read_msr(IA32_MSR_HV_SINT0 + VMBUS_SINT_TIMER);
msr = fInterruptVector | (msr & IA32_MSR_HV_SINT_RSVD_MASK);
x86_write_msr(IA32_MSR_HV_SINT0 + VMBUS_SINT_TIMER, msr);
TRACE("cpu%u: sint%u new msr 0x%llX\n", VMBUS_SINT_TIMER, cpu, (unsigned long long)msr);
// Enable interrupts
msr = x86_read_msr(IA32_MSR_HV_SCONTROL);
msr = (msr & IA32_MSR_HV_SCONTROL_RSVD_MASK) | IA32_MSR_HV_SCONTROL_ENABLE;
x86_write_msr(IA32_MSR_HV_SCONTROL, msr);
TRACE("cpu%u: scontrol new msr 0x%llX\n", cpu, (unsigned long long)msr);
}
void
VMBus::_DisableInterruptCPU(int32 cpu)
{
// Disable interrupts
uint64 msr = x86_read_msr(IA32_MSR_HV_SCONTROL);
msr &= IA32_MSR_HV_SCONTROL_RSVD_MASK;
x86_write_msr(IA32_MSR_HV_SCONTROL, msr);
TRACE("cpu%u: scontrol new msr 0x%llX\n", cpu, (unsigned long long)msr);
// Disable SIMP and SIEFP
msr = x86_read_msr(IA32_MSR_HV_SIMP);
msr &= IA32_MSR_HV_SIMP_RSVD_MASK;
x86_write_msr(IA32_MSR_HV_SIMP, msr);
TRACE("cpu%u: simp new msr 0x%llX\n", cpu, (unsigned long long)msr);
msr = x86_read_msr(IA32_MSR_HV_SIEFP);
msr &= IA32_MSR_HV_SIEFP_RSVD_MASK;
x86_write_msr(IA32_MSR_HV_SIEFP, msr);
TRACE("cpu%u: siefp new msr 0x%llX\n", cpu, (unsigned long long)msr);
// Mask interrupt vector for incoming VMBus messages
msr = x86_read_msr(IA32_MSR_HV_SINT0 + VMBUS_SINT_MESSAGE);
msr = IA32_MSR_HV_SINT_MASKED | (msr & IA32_MSR_HV_SINT_RSVD_MASK);
x86_write_msr(IA32_MSR_HV_SINT0 + VMBUS_SINT_MESSAGE, msr);
TRACE("cpu%u: sint%u new msr 0x%llX\n", VMBUS_SINT_MESSAGE, cpu, (unsigned long long)msr);
// Mask interrupt vector for VMBus timers
msr = x86_read_msr(IA32_MSR_HV_SINT0 + VMBUS_SINT_TIMER);
msr = IA32_MSR_HV_SINT_MASKED | (msr & IA32_MSR_HV_SINT_RSVD_MASK);
x86_write_msr(IA32_MSR_HV_SINT0 + VMBUS_SINT_TIMER, msr);
TRACE("cpu%u: sint%u new msr 0x%llX\n", VMBUS_SINT_TIMER, cpu, (unsigned long long)msr);
}
/*static*/ void
VMBus::_SignalEom(void*, int)
{
x86_write_msr(IA32_MSR_HV_EOM, 0);
}
@@ -0,0 +1,120 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HYPERV_x86_CPU_H_
#define _HYPERV_x86_CPU_H_
// CPUID leaves
#define IA32_CPUID_LEAF_HYPERVISOR 0x40000000
#define IA32_CPUID_LEAF_HV_INT_ID 0x40000001
#define IA32_CPUID_LEAF_HV_SYS_ID 0x40000002
#define IA32_CPUID_LEAF_HV_FEAT_ID 0x40000003
#define IA32_CPUID_LEAF_HV_IMP_RECOMMENDS 0x40000004
#define IA32_CPUID_LEAF_HV_IMP_LIMITS 0x40000005
#define IA32_CPUID_LEAF_HV_IMP_FEATURES 0x40000006
// Hyper-V signature "Hv#1"
#define HV_CPUID_INTERFACE_ID 0x31237648
// MSR registers
#define IA32_MSR_HV_GUEST_OS_ID 0x40000000
#define IA32_MSR_HV_HYPERCALL 0x40000001
#define IA32_MSR_HV_VP_INDEX 0x40000002
#define IA32_MSR_HV_RESET 0x40000003
#define IA32_MSR_HV_VP_RUNTIME 0x40000010
#define IA32_MSR_HV_TIME_REF_COUNT 0x40000020
#define IA32_MSR_HV_REFERENCE_TSC 0x40000021
#define IA32_MSR_HV_TSC_FREQUENCY 0x40000022
#define IA32_MSR_HV_APIC_FREQUENCY 0x40000023
#define IA32_MSR_HV_EOI 0x40000070
#define IA32_MSR_HV_ICR 0x40000071
#define IA32_MSR_HV_TPR 0x40000072
#define IA32_MSR_HV_APIC_ASSIST_PAGE 0x40000073
#define IA32_MSR_HV_SCONTROL 0x40000080
#define IA32_MSR_HV_SVERSION 0x40000081
#define IA32_MSR_HV_SIEFP 0x40000082
#define IA32_MSR_HV_SIMP 0x40000083
#define IA32_MSR_HV_EOM 0x40000084
#define IA32_MSR_HV_SINT0 0x40000090
#define IA32_MSR_HV_STIMER0_CONFIG 0x400000B0
#define IA32_MSR_HV_STIMER0_COUNT 0x400000B1
#define IA32_MSR_HV_GUEST_IDLE 0x400000f0
// MSR guest ID bits
#define IA32_MSR_HV_GUEST_OS_ID_BUILD_MASK 0x000000000000FFFFULL
#define IA32_MSR_HV_GUEST_OS_ID_VERSION_MASK 0x0000FFFFFFFF0000ULL
#define IA32_MSR_HV_GUEST_OS_ID_VERSION_SHIFT 16
#define IA32_MSR_HV_GUEST_OS_ID_OSID_MASK 0x00FF000000000000ULL
#define IA32_MSR_HV_GUEST_OS_ID_OSID_SHIFT 48
#define IA32_MSR_HV_GUEST_OS_ID_OSTYPE_MASK 0x7F00000000000000ULL
#define IA32_MSR_HV_GUEST_OS_ID_OSTYPE_SHIFT 56
#define IA32_MSR_HV_GUEST_OS_ID_OPEN_SOURCE (1ULL << 63)
// MSR hypercall bits
#define IA32_MSR_HV_HYPERCALL_ENABLE (1ULL << 0)
#define IA32_MSR_HV_HYPERCALL_RSVD_MASK 0x0FFEULL
#define IA32_MSR_HV_HYPERCALL_PAGE_SHIFT 12
// MSR SynIC control bits
#define IA32_MSR_HV_SCONTROL_ENABLE (1ULL << 0)
#define IA32_MSR_HV_SCONTROL_RSVD_MASK 0xFFFFFFFFFFFFFFFEULL
// MSR SIEFP bits
#define IA32_MSR_HV_SIEFP_ENABLE (1ULL << 0)
#define IA32_MSR_HV_SIEFP_RSVD_MASK 0x0FFEULL
#define IA32_MSR_HV_SIEFP_PAGE_SHIFT 12
#define IA32_MSR_HV_SIEFP_PAGE_MASK (~0ULL << IA32_MSR_HV_SIEFP_PAGE_SHIFT)
// MSR SIMP bits
#define IA32_MSR_HV_SIMP_ENABLE (1ULL << 0)
#define IA32_MSR_HV_SIMP_RSVD_MASK 0x0FFEULL
#define IA32_MSR_HV_SIMP_PAGE_SHIFT 12
#define IA32_MSR_HV_SIMP_PAGE_MASK (~0ULL << IA32_MSR_HV_SIMP_PAGE_SHIFT)
// MSR SINT bits
#define IA32_MSR_HV_SINT_VECTOR_MASK 0x00000000000000FFULL
#define IA32_MSR_HV_SINT_RSVD1_MASK 0x000000000000FF00ULL
#define IA32_MSR_HV_SINT_MASKED (1ULL << 16)
#define IA32_MSR_HV_SINT_AUTOEOI (1ULL << 17)
#define IA32_MSR_HV_SINT_POLLING (1ULL << 18)
#define IA32_MSR_HV_SINT_RSVD2_MASK 0xFFFFFFFFFFFC0000ULL
#define IA32_MSR_HV_SINT_RSVD_MASK \
(IA32_MSR_HV_SINT_RSVD1_MASK | IA32_MSR_HV_SINT_RSVD2_MASK)
// Version info reported to Hyper-V for hypercall purposes
// Report as FreeBSD 16.0, can optionally request a new OS type for Haiku
// https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/hypercall-interface#guest-os-identity-msr-for-open-source-operating-systems
#define HYPERV_FREEBSD_BUILD 0ULL
#define HYPERV_FREEBSD_VERSION 1600000ULL
#define HYPERV_FREEBSD_OSID 0ULL
#define HYPERV_FREEBSD_OSTYPE 0x2ULL
#define IA32_MSR_HV_GUEST_OS_ID_BUILD_FREEBSD \
(HYPERV_FREEBSD_BUILD & IA32_MSR_HV_GUEST_OS_ID_BUILD_MASK)
#define IA32_MSR_HV_GUEST_OS_ID_VERSION_FREEBSD \
((HYPERV_FREEBSD_VERSION << IA32_MSR_HV_GUEST_OS_ID_VERSION_SHIFT) \
& IA32_MSR_HV_GUEST_OS_ID_VERSION_MASK)
#define IA32_MSR_HV_GUEST_OS_ID_OSID_FREEBSD \
((HYPERV_FREEBSD_OSID << IA32_MSR_HV_GUEST_OS_ID_OSID_SHIFT) \
& IA32_MSR_HV_GUEST_OS_ID_OSID_MASK)
#define IA32_MSR_HV_GUEST_OS_ID_OSTYPE_FREEBSD \
((HYPERV_FREEBSD_OSTYPE << IA32_MSR_HV_GUEST_OS_ID_OSTYPE_SHIFT) \
& IA32_MSR_HV_GUEST_OS_ID_OSTYPE_MASK)
#define IA32_MSR_HV_GUEST_OS_ID_FREEBSD \
(IA32_MSR_HV_GUEST_OS_ID_BUILD_FREEBSD \
| IA32_MSR_HV_GUEST_OS_ID_VERSION_FREEBSD \
| IA32_MSR_HV_GUEST_OS_ID_OSID_FREEBSD \
| IA32_MSR_HV_GUEST_OS_ID_OSTYPE_FREEBSD \
| IA32_MSR_HV_GUEST_OS_ID_OPEN_SOURCE)
#endif // _HYPERV_x86_CPU_H_
@@ -0,0 +1,497 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HYPERV_SPEC_PRIVATE_H_
#define _HYPERV_SPEC_PRIVATE_H_
#include <hyperv_spec.h>
// Hyper-V message types
enum {
HYPERV_MSGTYPE_NONE = 0x00000000,
HYPERV_MSGTYPE_CHANNEL = 0x00000001,
HYPERV_MSGTYPE_TIMER_EXPIRED = 0x80000010
};
// Hypercall status
enum {
HYPERCALL_STATUS_SUCCESS = 0x0000,
HYPERCALL_STATUS_INVALID_HYPERCALL_CODE = 0x0002,
HYPERCALL_STATUS_INVALID_HYPERCALL_INPUT = 0x0003,
HYPERCALL_STATUS_INVALID_ALIGNMENT = 0x0004,
HYPERCALL_STATUS_INVALID_PARAMETER = 0x0005,
HYPERCALL_STATUS_ACCESS_DENIED = 0x0006,
HYPERCALL_STATUS_INVALID_PARTITION_STATE = 0x0007,
HYPERCALL_STATUS_OPERATION_DENIED = 0x0008,
HYPERCALL_STATUS_UNKNOWN_PROPERTY = 0x0009,
HYPERCALL_STATUS_PROPERTY_VALUE_OUT_OF_RANGE = 0x000A,
HYPERCALL_STATUS_INSUFFICIENT_MEMORY = 0x000B,
HYPERCALL_STATUS_PARTITION_TOO_DEEP = 0x000C,
HYPERCALL_STATUS_INVALID_PARTITION_ID = 0x000D,
HYPERCALL_STATUS_INVALID_VP_INDEX = 0x000E,
HYPERCALL_STATUS_INVALID_PORT_ID = 0x0011,
HYPERCALL_STATUS_INVALID_CONNECTION_ID = 0x0012,
HYPERCALL_STATUS_INSUFFICIENT_BUFFERS = 0x0013,
HYPERCALL_STATUS_NOT_ACKNOWLEDGED = 0x0014,
HYPERCALL_STATUS_ACKNOWLEDGED = 0x0016,
HYPERCALL_STATUS_INVALID_SAVE_RESTORE_STATE = 0x0017,
HYPERCALL_STATUS_INVALID_SYNIC_STATE = 0x0018,
HYPERCALL_STATUS_OBJECT_IN_USE = 0x0019,
HYPERCALL_STATUS_INVALID_PROXIMITY_DOMAIN_INFO = 0x001A,
HYPERCALL_STATUS_NO_DATA = 0x001B,
HYPERCALL_STATUS_INACTIVE = 0x001C,
HYPERCALL_STATUS_NO_RESOURCES = 0x001D,
HYPERCALL_STATUS_FEATURE_UNAVAILABLE = 0x001E,
HYPERCALL_STATUS_PARTIAL_PACKET = 0x001F
};
// Slow memory-based hypercall for VMBus messaging
#define HYPERCALL_POST_MESSAGE 0x0005C
// Fast register-based hypercall for VMBus events
#define HYPERCALL_SIGNAL_EVENT 0x1005D
// Maximum size for a hypercall message
#define HYPERCALL_MAX_DATA_SIZE 240
#define HYPERCALL_MAX_SIZE 256
// Maximum hypercall retry count
#define HYPERCALL_MAX_RETRY_COUNT 20
// Hypercall post message input parameters
// https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/hypercalls/hvcallpostmessage
typedef struct {
uint32 connection_id;
uint32 reserved;
uint32 message_type;
uint32 data_size;
uint8 data[HYPERCALL_MAX_DATA_SIZE];
} _PACKED hypercall_post_msg_input;
#define HYPERV_SYNIC_MAX_INTS 16
// SynIC message page
// https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/inter-partition-communication#sim-page
#define HV_MESSAGE_FLAGS_PENDING (1 << 0)
#define HV_MESSAGE_DATA_SIZE 240
#define HV_MESSAGE_SIZE 256
// Per-interrupt message data
typedef struct {
uint32 message_type;
uint8 payload_size;
uint8 message_flags;
uint16 reserved1;
union {
uint64 origination_id;
struct {
uint32 port_id : 24;
uint32 reserved2 : 8;
};
};
uint8 data[HV_MESSAGE_DATA_SIZE];
} _PACKED hv_message;
// All interrupts message data
typedef struct {
hv_message interrupts[HYPERV_SYNIC_MAX_INTS];
} hv_message_page;
HV_STATIC_ASSERT(sizeof(hv_message_page) == HV_PAGE_SIZE, "hv_message_page size mismatch");
// SynIC event flags
// https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/inter-partition-communication#synic-event-flags
#define HV_EVENT_FLAGS_SIZE 256
#define HV_EVENT_FLAGS_COUNT (HV_EVENT_FLAGS_SIZE * 8)
// Per-interrupt event flags
typedef union {
uint8 flags[HV_EVENT_FLAGS_SIZE];
uint32 flags32[HV_EVENT_FLAGS_SIZE / sizeof(uint32)];
} hv_event_flags;
// All interrupts event flags
typedef struct {
hv_event_flags interrupts[HYPERV_SYNIC_MAX_INTS];
} hv_event_flags_page;
HV_STATIC_ASSERT(sizeof(hv_event_flags_page) == HV_PAGE_SIZE, "hv_event_flags_page size mismatch");
// HID of VMBus ACPI device
// This is normally just "VMBus", but acpica seems to need all caps
#define VMBUS_ACPI_HID_NAME "VMBUS"
// Fixed interrupt for VMBus messages
#define VMBUS_SINT_MESSAGE 2
// Fixed interrupt for VMBus timers
#define VMBUS_SINT_TIMER 4
// Fixed connection ID for messages
#define VMBUS_CONNID_MESSAGE 1
// Fixed connection ID for events
#define VMBUS_CONNID_EVENTS 2
// Max channels is 2048 on Server 2012 and newer
// Server 2008 and 2008 R2 have a maximum of 256
#define VMBUS_MAX_CHANNELS HV_EVENT_FLAGS_COUNT
#define VMBUS_MAX_CHANNELS_LEGACY 256
// Channel IDs start at 1, 0 is considered an invalid channel ID by Hyper-V
#define VMBUS_CHANNEL_ID 0
// Ordered list of newest to oldest VMBus versions when connecting
static const uint32 vmbus_versions[] = {
VMBUS_VERSION_WS2022,
VMBUS_VERSION_WIN10_RS5_WS2019,
VMBUS_VERSION_WIN10_RS4,
VMBUS_VERSION_WIN10_V5,
VMBUS_VERSION_WIN10_RS3,
VMBUS_VERSION_WIN10_RS1_WS2016,
VMBUS_VERSION_WIN81_WS2012R2,
VMBUS_VERSION_WIN8_WS2012,
VMBUS_VERSION_WS2008R2,
VMBUS_VERSION_WS2008
};
typedef struct {
uint32 data1;
uint16 data2;
uint16 data3;
uint8 data4[8];
} _PACKED vmbus_guid_t;
// VMBus GPADL range descriptor
typedef struct {
uint32 length;
uint32 offset;
uint64 page_nums[];
} _PACKED vmbus_gpadl_range;
#define VMBUS_GPADL_NULL 0
#define VMBUS_GPADL_MAX_PAGES 8192
// VMBus RX and TX event flags page
typedef struct {
hv_event_flags rx_event_flags;
uint8 reserved1[(HV_PAGE_SIZE / 2) - sizeof(rx_event_flags)];
hv_event_flags tx_event_flags;
uint8 reserved2[(HV_PAGE_SIZE / 2) - sizeof(tx_event_flags)];
} vmbus_event_flags_page;
HV_STATIC_ASSERT(sizeof(vmbus_event_flags_page) == HV_PAGE_SIZE,
"vmbus_event_flags_page size mismatch");
// VMBus message type
enum {
VMBUS_MSGTYPE_INVALID = 0,
VMBUS_MSGTYPE_CHANNEL_OFFER = 1,
VMBUS_MSGTYPE_RESCIND_CHANNEL_OFFER = 2,
VMBUS_MSGTYPE_REQUEST_CHANNELS = 3,
VMBUS_MSGTYPE_REQUEST_CHANNELS_DONE = 4,
VMBUS_MSGTYPE_OPEN_CHANNEL = 5,
VMBUS_MSGTYPE_OPEN_CHANNEL_RESPONSE = 6,
VMBUS_MSGTYPE_CLOSE_CHANNEL = 7,
VMBUS_MSGTYPE_CREATE_GPADL = 8,
VMBUS_MSGTYPE_CREATE_GPADL_ADDITIONAL = 9,
VMBUS_MSGTYPE_CREATE_GPADL_RESPONSE = 10,
VMBUS_MSGTYPE_FREE_GPADL = 11,
VMBUS_MSGTYPE_FREE_GPADL_RESPONSE = 12,
VMBUS_MSGTYPE_FREE_CHANNEL = 13,
VMBUS_MSGTYPE_CONNECT = 14,
VMBUS_MSGTYPE_CONNECT_RESPONSE = 15,
VMBUS_MSGTYPE_DISCONNECT = 16,
VMBUS_MSGTYPE_DISCONNECT_RESPONSE = 17,
VMBUS_MSGTYPE_MODIFY_CHANNEL = 22,
VMBUS_MSGTYPE_MODIFY_CHANNEL_RESPONSE = 24,
VMBUS_MSGTYPE_MAX
};
// VMBus message header
typedef struct {
uint32 type;
uint32 reserved;
} _PACKED vmbus_msg_header;
// VMBus channel offer message from Hyper-V
typedef struct {
vmbus_msg_header header;
vmbus_guid_t type_id;
vmbus_guid_t instance_id;
uint64 reserved1[2];
uint16 flags;
uint16 mmio_size_mb;
union {
struct {
#define VMBUS_CHANNEL_OFFER_MAX_USER_BYTES 120
uint8 data[VMBUS_CHANNEL_OFFER_MAX_USER_BYTES];
} standard;
struct {
uint32 mode;
uint8 data[VMBUS_CHANNEL_OFFER_MAX_USER_BYTES - 4];
} pipe;
};
uint16 sub_index;
uint16 reserved2;
uint32 channel_id;
uint8 monitor_id;
// Fields present only in Server 2008 R2 and newer
uint8 monitor_alloc : 1;
uint8 reserved3 : 7;
uint16 dedicated_int : 1;
uint16 reserved4 : 15;
uint32 connection_id;
} _PACKED vmbus_msg_channel_offer;
// VMBus rescind channel offer message from Hyper-V
typedef struct {
vmbus_msg_header header;
uint32 channel_id;
} _PACKED vmbus_msg_rescind_channel_offer;
// VMBus request channels message to Hyper-V
typedef struct {
vmbus_msg_header header;
} _PACKED vmbus_msg_request_channels;
// VMBus request channels done message from Hyper-V
typedef struct {
vmbus_msg_header header;
} _PACKED vmbus_msg_request_channels_done;
// VMBus open channel message to Hyper-V
typedef struct {
vmbus_msg_header header;
uint32 channel_id;
uint32 open_id;
uint32 gpadl_id;
uint32 target_cpu;
uint32 rx_page_offset;
uint8 user_data[VMBUS_CHANNEL_OFFER_MAX_USER_BYTES];
} _PACKED vmbus_msg_open_channel;
// VMBus open channel response message from Hyper-V
typedef struct {
vmbus_msg_header header;
uint32 channel_id;
uint32 open_id;
uint32 result;
} _PACKED vmbus_msg_open_channel_resp;
// VMBus close channel message to Hyper-V
typedef struct {
vmbus_msg_header header;
uint32 channel_id;
} _PACKED vmbus_msg_close_channel;
// VMBus create GPADL message to Hyper-V
typedef struct {
vmbus_msg_header header;
uint32 channel_id;
uint32 gpadl_id;
uint16 total_range_length;
uint16 range_count;
vmbus_gpadl_range ranges[1]; // Only 1 range is supported by this driver
} _PACKED vmbus_msg_create_gpadl;
#define VMBUS_MSG_CREATE_GPADL_MAX_PAGES \
((HYPERCALL_MAX_DATA_SIZE - sizeof(vmbus_msg_create_gpadl)) / sizeof(uint64))
// VMBus create GPADL additional pages message to Hyper-V
typedef struct {
vmbus_msg_header header;
uint32 msg_num;
uint32 gpadl_id;
uint64 page_nums[];
} _PACKED vmbus_msg_create_gpadl_additional;
#define VMBUS_MSG_CREATE_GPADL_ADDITIONAL_MAX_PAGES \
((HYPERCALL_MAX_DATA_SIZE - sizeof(vmbus_msg_create_gpadl_additional)) / sizeof(uint64))
// VMBus create GPADL response message from Hyper-V
typedef struct {
vmbus_msg_header header;
uint32 channel_id;
uint32 gpadl_id;
uint32 result;
} _PACKED vmbus_msg_create_gpadl_resp;
// VMBus free GPADL message to Hyper-V
typedef struct {
vmbus_msg_header header;
uint32 channel_id;
uint32 gpadl_id;
} _PACKED vmbus_msg_free_gpadl;
// VMBus free GPADL message from Hyper-V
typedef struct {
vmbus_msg_header header;
uint32 gpadl_id;
} _PACKED vmbus_msg_free_gpadl_resp;
// VMBus free channel message to Hyper-V
typedef struct {
vmbus_msg_header header;
uint32 channel_id;
} _PACKED vmbus_msg_free_channel;
// VMBus connect message to Hyper-V
typedef struct {
vmbus_msg_header header;
uint32 version;
uint32 target_cpu;
uint64 event_flags_physaddr;
uint64 monitor1_physaddr;
uint64 monitor2_physaddr;
} _PACKED vmbus_msg_connect;
// VMBus connect response message from Hyper-V
typedef struct {
vmbus_msg_header header;
uint8 supported;
uint8 connection_state;
uint16 reserved;
uint32 connection_id;
} _PACKED vmbus_msg_connect_resp;
// VMBus disconnect message to Hyper-V
typedef struct {
vmbus_msg_header header;
} _PACKED vmbus_msg_disconnect;
// VMBus disconnect response message from Hyper-V
typedef struct {
vmbus_msg_header header;
} _PACKED vmbus_msg_disconnect_resp;
// VMBus combined message
typedef union {
vmbus_msg_header header;
vmbus_msg_channel_offer channel_offer;
vmbus_msg_rescind_channel_offer rescind_channel_offer;
vmbus_msg_request_channels request_channels;
vmbus_msg_request_channels_done request_channels_done;
vmbus_msg_open_channel open_channel;
vmbus_msg_open_channel_resp open_channel_resp;
vmbus_msg_close_channel close_channel;
vmbus_msg_create_gpadl create_gpadl;
vmbus_msg_create_gpadl_additional create_gpadl_additional;
vmbus_msg_create_gpadl_resp create_gpadl_resp;
vmbus_msg_free_gpadl free_gpadl;
vmbus_msg_free_gpadl_resp free_gpadl_resp;
vmbus_msg_free_channel free_channel;
vmbus_msg_connect connect;
vmbus_msg_connect_resp connect_resp;
vmbus_msg_disconnect disconnect;
vmbus_msg_disconnect_resp disconnect_resp;
} _PACKED vmbus_msg;
// VMBus message type to message length lookup
static const uint32 vmbus_msg_lengths[VMBUS_MSGTYPE_MAX] = {
0, // VMBUS_MSGTYPE_INVALID
sizeof(vmbus_msg_channel_offer), // VMBUS_MSGTYPE_CHANNEL_OFFER
sizeof(vmbus_msg_rescind_channel_offer), // VMBUS_MSGTYPE_RESCIND_CHANNEL_OFFER
sizeof(vmbus_msg_request_channels), // VMBUS_MSGTYPE_REQUEST_CHANNELS
sizeof(vmbus_msg_request_channels_done), // VMBUS_MSGTYPE_REQUEST_CHANNELS_DONE
sizeof(vmbus_msg_open_channel), // VMBUS_MSGTYPE_OPEN_CHANNEL
sizeof(vmbus_msg_open_channel_resp), // VMBUS_MSGTYPE_OPEN_CHANNEL_RESPONSE
sizeof(vmbus_msg_close_channel), // VMBUS_MSGTYPE_CLOSE_CHANNEL
0, // VMBUS_MSGTYPE_CREATE_GPADL
0, // VMBUS_MSGTYPE_CREATE_GPADL_PAGES
sizeof(vmbus_msg_create_gpadl_resp), // VMBUS_MSGTYPE_CREATE_GPADL_RESPONSE
sizeof(vmbus_msg_free_gpadl), // VMBUS_MSGTYPE_FREE_GPADL
sizeof(vmbus_msg_free_gpadl_resp), // VMBUS_MSGTYPE_FREE_GPADL_RESPONSE
sizeof(vmbus_msg_free_channel), // VMBUS_MSGTYPE_FREE_CHANNEL
sizeof(vmbus_msg_connect), // VMBUS_MSGTYPE_CONNECT
sizeof(vmbus_msg_connect_resp), // VMBUS_MSGTYPE_CONNECT_RESPONSE
sizeof(vmbus_msg_disconnect), // VMBUS_MSGTYPE_DISCONNECT
sizeof(vmbus_msg_disconnect_resp), // VMBUS_MSGTYPE_DISCONNECT_RESPONSE
0, // 18
0, // 19
0, // 20
0, // 21
0, // VMBUS_MSGTYPE_MODIFY_CHANNEL
0, // 23
0 // VMBUS_MSGTYPE_MODIFY_CHANNEL_RESPONSE
};
// VMBus ring buffer structure
typedef struct {
volatile uint32 write_index;
volatile uint32 read_index;
volatile uint32 interrupt_mask;
volatile uint32 pending_send_size;
uint32 reserved[12];
union {
struct {
uint32 pending_send_size_supported : 1;
};
uint32 value;
} features;
// Padding to ensure data buffer is page-aligned.
uint8 padding[HV_PAGE_SIZE - 76];
volatile uint64 guest_to_host_interrupt_count;
uint8 buffer[];
} vmbus_ring_buffer;
HV_STATIC_ASSERT(sizeof(vmbus_ring_buffer) == HV_PAGE_SIZE, "vmbus_ring_buffer size mismatch");
#endif // _HYPERV_SPEC_PRIVATE_H_