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:
@@ -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
|
||||
|
||||
@@ -318,6 +318,7 @@ AddBootModuleSymlinksToPackage
|
||||
dpc
|
||||
efi_gpt
|
||||
generic_ide_pci
|
||||
hyperv@x86,x86_64
|
||||
ide_isa@x86
|
||||
isa@x86,x86_64
|
||||
intel
|
||||
|
||||
@@ -319,6 +319,7 @@ AddBootModuleSymlinksToPackage
|
||||
virtio_pci
|
||||
virtio_block
|
||||
virtio_scsi
|
||||
hyperv@x86,x86_64
|
||||
efi_gpt
|
||||
intel
|
||||
bfs
|
||||
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
Reference in New Issue
Block a user