hyperv: Implement heartbeat support

Adds support for the Hyper-V heartbeat integration component as part
of a new hyperv_ic driver.

Change-Id: I130ccc946d1f3ea30d87abf403d23c2cc34a1c57
Reviewed-on: https://review.haiku-os.org/c/haiku/+/10427
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: Jérôme Duval <[email protected]>
This commit is contained in:
John Davis
2026-03-13 14:06:07 +00:00
committed by Jérôme Duval
parent e438946e25
commit a0c9ad5269
16 changed files with 698 additions and 0 deletions
+1
View File
@@ -665,6 +665,7 @@ rule ArchitectureSetupWarnings architecture
EnableWerror src add-ons kernel drivers display ;
EnableWerror src add-ons kernel drivers dvb ;
EnableWerror src add-ons kernel drivers graphics ;
EnableWerror src add-ons kernel drivers hyperv ;
EnableWerror src add-ons kernel drivers input ;
EnableWerror src add-ons kernel drivers joystick ;
EnableWerror src add-ons kernel drivers midi ;
+3
View File
@@ -154,6 +154,9 @@ AddNewDriversToPackage display :
AddNewDriversToPackage audio hmulti :
# virtio_sound
;
AddNewDriversToPackage hyperv :
hyperv_ic@x86,x86_64
;
# legacy drivers
AddDriversToPackage :
+3
View File
@@ -99,6 +99,9 @@ AddNewDriversToPackage network :
AddNewDriversToPackage display :
# display_adapter@x86
;
AddNewDriversToPackage hyperv :
hyperv_ic@x86,x86_64
;
# legacy drivers
AddDriversToPackage :
+1
View File
@@ -7,6 +7,7 @@ SubInclude HAIKU_TOP src add-ons kernel drivers common ;
SubInclude HAIKU_TOP src add-ons kernel drivers disk ;
SubInclude HAIKU_TOP src add-ons kernel drivers display ;
SubInclude HAIKU_TOP src add-ons kernel drivers dvb ;
SubInclude HAIKU_TOP src add-ons kernel drivers hyperv ;
SubInclude HAIKU_TOP src add-ons kernel drivers input ;
SubInclude HAIKU_TOP src add-ons kernel drivers joystick ;
SubInclude HAIKU_TOP src add-ons kernel drivers graphics ;
@@ -0,0 +1,3 @@
SubDir HAIKU_TOP src add-ons kernel drivers hyperv ;
SubInclude HAIKU_TOP src add-ons kernel drivers hyperv hyperv_ic ;
@@ -0,0 +1,248 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "ICBase.h"
//#define TRACE_HYPERV_IC
#ifdef TRACE_HYPERV_IC
# define TRACE(x...) dprintf("\33[94mhyperv_ic:\33[0m " x)
#else
# define TRACE(x...) ;
#endif
#define TRACE_ALWAYS(x...) dprintf("\33[94mhyperv_ic:\33[0m " x)
#define ERROR(x...) dprintf("\33[94mhyperv_ic:\33[0m " x)
#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
ICBase::ICBase(device_node* node, uint32 packetLength, uint16 messageType,
const uint32* messageVersions, uint32 messageVersionCount)
:
fStatus(B_NO_INIT),
fNode(node),
fFrameworkVersion(0),
fMessageVersion(0),
fPacket(NULL),
fPacketLength(packetLength),
fMessageType(messageType),
fMessageVersions(messageVersions),
fMessageVersionCount(messageVersionCount),
fHyperV(NULL),
fHyperVCookie(NULL)
{
CALLED();
device_node* parent = gDeviceManager->get_parent_node(node);
gDeviceManager->get_driver(parent, (driver_module_info**)&fHyperV, (void**)&fHyperVCookie);
gDeviceManager->put_node(parent);
fPacket = static_cast<uint8*>(malloc(fPacketLength));
if (fPacket == NULL) {
fStatus = B_NO_MEMORY;
return;
}
fStatus = B_OK;
}
ICBase::~ICBase()
{
CALLED();
free(fPacket);
}
status_t
ICBase::Connect(uint32 txLength, uint32 rxLength)
{
CALLED();
status_t status = fHyperV->open(fHyperVCookie, txLength, rxLength, _CallbackHandler, this);
if (status != B_OK) {
ERROR("Failed to open channel");
return status;
}
return B_OK;
}
void
ICBase::Disconnect()
{
CALLED();
fHyperV->close(fHyperVCookie);
}
/*virtual*/ void
ICBase::OnProtocolNegotiated()
{
// Default implementation
}
/*virtual*/ void
ICBase::OnMessageSent(hv_ic_msg* icMessage)
{
// Default implementation
}
status_t
ICBase::_NegotiateProtocol(hv_ic_msg_negotiate* message)
{
CALLED();
if (message->header.data_length < offsetof(hv_ic_msg_negotiate, versions[2])
- sizeof(message->header)) {
ERROR("IC[%u] invalid negotiate msg length 0x%X\n", fMessageType,
message->header.data_length);
return B_BAD_VALUE;
}
if (message->framework_version_count == 0 || message->message_version_count == 0) {
ERROR("IC[%u] invalid negotiate msg version count\n", fMessageType);
return B_BAD_VALUE;
}
uint32 versionCount = message->framework_version_count + message->message_version_count;
if (versionCount < 2) {
ERROR("IC[%u] invalid negotiate msg version count\n", fMessageType);
return B_BAD_VALUE;
}
// Match the highest supported framework version
bool foundFrameworkVersion = false;
for (uint32 i = 0; i < hv_framework_version_count; i++) {
for (uint32 j = 0; j < message->framework_version_count; j++) {
TRACE("IC[%u] checking fw version %u.%u against %u.%u\n", fMessageType,
GET_IC_VERSION_MAJOR(hv_framework_versions[i]),
GET_IC_VERSION_MINOR(hv_framework_versions[i]),
GET_IC_VERSION_MAJOR(message->versions[j]),
GET_IC_VERSION_MINOR(message->versions[j]));
if (hv_framework_versions[i] == message->versions[j]) {
fFrameworkVersion = hv_framework_versions[i];
foundFrameworkVersion = true;
break;
}
}
if (foundFrameworkVersion)
break;
}
// Match the highest supported message version
bool foundMessageVersion = false;
for (uint32 i = 0; i < fMessageVersionCount; i++) {
for (uint32 j = message->message_version_count; j < versionCount; j++) {
TRACE("IC[%u] checking msg version %u.%u against %u.%u\n", fMessageType,
GET_IC_VERSION_MAJOR(fMessageVersions[i]),
GET_IC_VERSION_MINOR(fMessageVersions[i]),
GET_IC_VERSION_MAJOR(message->versions[j]),
GET_IC_VERSION_MINOR(message->versions[j]));
if (fMessageVersions[i] == message->versions[j]) {
fMessageVersion = fMessageVersions[i];
foundMessageVersion = true;
break;
}
}
if (foundMessageVersion)
break;
}
if (!foundFrameworkVersion || !foundMessageVersion) {
ERROR("IC%u unsupported versions\n", fMessageType);
message->framework_version_count = 0;
message->message_version_count = 0;
return B_UNSUPPORTED;
}
TRACE("IC[%u] found supported fw version %u.%u msg version %u.%u\n", fMessageType,
GET_IC_VERSION_MAJOR(fFrameworkVersion), GET_IC_VERSION_MINOR(fFrameworkVersion),
GET_IC_VERSION_MAJOR(fMessageVersion), GET_IC_VERSION_MINOR(fMessageVersion));
message->framework_version_count = 1;
message->message_version_count = 1;
message->versions[0] = fFrameworkVersion;
message->versions[1] = fMessageVersion;
return B_OK;
}
/*static*/ void
ICBase::_CallbackHandler(void* arg)
{
ICBase* icBaseDevice = reinterpret_cast<ICBase*>(arg);
icBaseDevice->_Callback();
}
void
ICBase::_Callback()
{
while (true) {
uint32 length = fPacketLength;
uint32 headerLength;
uint32 dataLength;
status_t status = fHyperV->read_packet(fHyperVCookie, fPacket, &length,
&headerLength, &dataLength);
if (status == B_DEV_NOT_READY) {
break;
} else if (status != B_OK) {
ERROR("IC[%u] failed to read packet (%s)\n", fMessageType, strerror(status));
break;
}
if (dataLength < sizeof(hv_ic_msg_header)) {
ERROR("IC[%u] invalid packet\n", fMessageType);
continue;
}
vmbus_pkt_header* header = reinterpret_cast<vmbus_pkt_header*>(fPacket);
hv_ic_msg* message = reinterpret_cast<hv_ic_msg*>(fPacket + headerLength);
if (message->header.data_length <= dataLength - sizeof(message->header)) {
if (message->header.type == HV_IC_MSGTYPE_NEGOTIATE) {
// IC protocol negotiation
status = _NegotiateProtocol(&message->negotiate);
if (status == B_OK) {
OnProtocolNegotiated();
} else {
ERROR("IC[%u] protocol negotiation failed (%s)\n", fMessageType,
strerror(status));
message->header.status = HV_IC_STATUS_FAILED;
}
} else if (message->header.type == fMessageType) {
// IC device-specific message
OnMessageReceived(message);
} else {
ERROR("IC[%u] unknown message type %u\n", fMessageType, message->header.type);
message->header.status = HV_IC_STATUS_FAILED;
}
} else {
ERROR("IC[%u] invalid msg data length 0x%X pkt length 0x%X\n", fMessageType,
message->header.data_length, dataLength);
message->header.status = HV_IC_STATUS_FAILED;
}
// Always respond to Hyper-V with the same packet that was originally received
message->header.flags = HV_IC_FLAG_TRANSACTION | HV_IC_FLAG_RESPONSE;
status = fHyperV->write_packet(fHyperVCookie, VMBUS_PKTTYPE_DATA_INBAND, message,
sizeof(hv_ic_msg_header) + message->header.data_length, false, header->transaction_id);
if (status == B_OK)
OnMessageSent(message);
else
ERROR("IC[%u] failed to send message (%s)\n", fMessageType, strerror(status));
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HYPERV_IC_BASE_H_
#define _HYPERV_IC_BASE_H_
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hyperv.h>
#include <hyperv_spec.h>
#include "ICDriver.h"
#include "ICProtocol.h"
class ICBase {
public:
ICBase(device_node* node, uint32 packetLength, uint16 messageType,
const uint32* messageVersions, uint32 messageVersionCount);
virtual ~ICBase();
status_t InitCheck() const { return fStatus; }
protected:
status_t Connect(uint32 txLength, uint32 rxLength);
void Disconnect();
virtual void OnProtocolNegotiated();
virtual void OnMessageReceived(hv_ic_msg* icMessage) = 0;
virtual void OnMessageSent(hv_ic_msg* icMessage);
protected:
status_t fStatus;
device_node* fNode;
uint32 fFrameworkVersion;
uint32 fMessageVersion;
private:
status_t _NegotiateProtocol(hv_ic_msg_negotiate* message);
static void _CallbackHandler(void* arg);
void _Callback();
private:
uint8* fPacket;
uint32 fPacketLength;
uint16 fMessageType;
const uint32* fMessageVersions;
uint32 fMessageVersionCount;
hyperv_device_interface* fHyperV;
hyperv_device fHyperVCookie;
};
#endif // _HYPERV_IC_BASE_H_
@@ -0,0 +1,22 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "ICDriver.h"
device_manager_info* gDeviceManager;
module_dependency module_dependencies[] = {
{ B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager },
{}
};
module_info* modules[] = {
(module_info*)&gHyperVHeartbeatDriverModule,
NULL
};
@@ -0,0 +1,18 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HYPERV_IC_DRIVER_H_
#define _HYPERV_IC_DRIVER_H_
#include <device_manager.h>
#include <KernelExport.h>
extern device_manager_info* gDeviceManager;
extern driver_module_info gHyperVHeartbeatDriverModule;
#endif // _HYPERV_IC_DRIVER_H_
@@ -0,0 +1,90 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HYPERV_IC_PROTOCOL_H_
#define _HYPERV_IC_PROTOCOL_H_
#define MAKE_IC_VERSION(major, minor) ((((minor) << 16) & 0xFFFF0000) | ((major) & 0x0000FFFF))
#define GET_IC_VERSION_MAJOR(version) ((version) & 0xFFFF)
#define GET_IC_VERSION_MINOR(version) (((version) >> 16) & 0xFFFF)
// IC framework versions
#define HV_IC_VERSION_2008 MAKE_IC_VERSION(1, 0)
#define HV_IC_VERSION_V3 MAKE_IC_VERSION(3, 0)
static const uint32 hv_framework_versions[] = {
HV_IC_VERSION_V3,
HV_IC_VERSION_2008
};
static const uint32 hv_framework_version_count = sizeof(hv_framework_versions)
/ sizeof(hv_framework_versions[0]);
// IC message types
enum {
HV_IC_MSGTYPE_NEGOTIATE = 0,
HV_IC_MSGTYPE_HEARTBEAT = 1,
HV_IC_MSGTYPE_KVP = 2,
HV_IC_MSGTYPE_SHUTDOWN = 3,
HV_IC_MSGTYPE_TIMESYNC = 4,
HV_IC_MSGTYPE_VSS = 5,
HV_IC_MSGTYPE_FILECOPY = 7
};
// IC message flags
#define HV_IC_FLAG_TRANSACTION (1 << 0)
#define HV_IC_FLAG_REQUEST (1 << 1)
#define HV_IC_FLAG_RESPONSE (1 << 2)
// IC status
enum {
HV_IC_STATUS_OK = 0x0,
HV_IC_STATUS_FAILED = 0x80004005,
HV_IC_STATUS_TIMEOUT = 0x800705B4,
HV_IC_STATUS_INVALID_ARG = 0x80070057,
HV_IC_STATUS_ALREADY_EXISTS = 0x80070050,
HV_IC_STATUS_DISK_FULL = 0x80070070
};
// IC message header
typedef struct {
uint32 pipe_flags;
uint32 pipe_messages;
uint32 framework_version;
uint16 type;
uint32 message_version;
uint16 data_length;
uint32 status;
uint8 transaction_id;
uint8 flags;
uint16 reserved;
} _PACKED hv_ic_msg_header;
// IC negotiation message
typedef struct {
hv_ic_msg_header header;
uint16 framework_version_count;
uint16 message_version_count;
uint32 reserved;
uint32 versions[];
} _PACKED hv_ic_msg_negotiate;
// IC combined message
typedef struct {
union {
hv_ic_msg_header header;
hv_ic_msg_negotiate negotiate;
};
} _PACKED hv_ic_msg;
#endif // _HYPERV_IC_PROTOCOL_H_
@@ -0,0 +1,14 @@
SubDir HAIKU_TOP src add-ons kernel drivers hyperv hyperv_ic ;
UsePrivateKernelHeaders ;
UsePrivateHeaders drivers hyperv ;
KernelAddon hyperv_ic :
ICBase.cpp
ICDriver.cpp
:
hyperv_ic_heartbeat.a
;
SubInclude HAIKU_TOP src add-ons kernel drivers hyperv hyperv_ic heartbeat ;
@@ -0,0 +1,42 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "Heartbeat.h"
Heartbeat::Heartbeat(device_node* node)
: ICBase(node, HV_HEARTBEAT_PKT_BUFFER_SIZE, HV_IC_MSGTYPE_HEARTBEAT, hv_heartbeat_versions,
hv_heartbeat_version_count)
{
CALLED();
fStatus = Connect(HV_HEARTBEAT_RING_SIZE, HV_HEARTBEAT_RING_SIZE);
}
void
Heartbeat::OnProtocolNegotiated()
{
TRACE("Heartbeat protocol version %u.%u\n", GET_IC_VERSION_MAJOR(fMessageVersion),
GET_IC_VERSION_MINOR(fMessageVersion));
}
void
Heartbeat::OnMessageReceived(hv_ic_msg* icMessage)
{
hv_heartbeat_msg* message = reinterpret_cast<hv_heartbeat_msg*>(icMessage);
if (message->header.data_length < offsetof(hv_heartbeat_msg, sequence)
- sizeof(message->header)) {
ERROR("Heartbeat msg invalid length 0x%X\n", message->header.data_length);
message->header.status = HV_IC_STATUS_FAILED;
return;
}
// Guest and host alternate incrementing the sequence number, typically every two seconds
// Hyper-V will report the guest as healthy so long as this continues
TRACE("Heartbeat sequence %" B_PRIu64 "\n", message->sequence);
message->sequence++;
}
@@ -0,0 +1,37 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HYPERV_HEARTBEAT_H_
#define _HYPERV_HEARTBEAT_H_
#include "ICBase.h"
#include "HeartbeatProtocol.h"
//#define TRACE_HYPERV_HEARTBEAT
#ifdef TRACE_HYPERV_HEARTBEAT
# define TRACE(x...) dprintf("\33[94mhyperv_heartbeat:\33[0m " x)
#else
# define TRACE(x...) ;
#endif
#define TRACE_ALWAYS(x...) dprintf("\33[94mhyperv_heartbeat:\33[0m " x)
#define ERROR(x...) dprintf("\33[94mhyperv_heartbeat:\33[0m " x)
#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
#define HYPERV_HEARTBEAT_DRIVER_MODULE_NAME "drivers/hyperv/hyperv_ic/heartbeat/driver_v1"
class Heartbeat : public ICBase {
public:
Heartbeat(device_node* node);
protected:
void OnProtocolNegotiated();
void OnMessageReceived(hv_ic_msg* icMessage);
};
#endif // _HYPERV_HEARTBEAT_H_
@@ -0,0 +1,100 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "Heartbeat.h"
static float
hyperv_heartbeat_supports_device(device_node* parent)
{
CALLED();
// Check if parent is the Hyper-V bus manager
const char* bus;
if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false) != B_OK)
return -1;
if (strcmp(bus, HYPERV_BUS_NAME) != 0)
return 0.0f;
// Check if parent is a Hyper-V heartbeat device
const char* type;
if (gDeviceManager->get_attr_string(parent, HYPERV_DEVICE_TYPE_STRING_ITEM, &type, false)
!= B_OK)
return 0.0f;
if (strcmp(type, VMBUS_TYPE_HEARTBEAT) != 0)
return 0.0f;
TRACE("Hyper-V Heartbeat device found!\n");
return 0.8f;
}
static status_t
hyperv_heartbeat_register_device(device_node* parent)
{
CALLED();
device_attr attributes[] = {
{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE,
{ .string = HYPERV_PRETTYNAME_HEARTBEAT }},
{ NULL }
};
return gDeviceManager->register_node(parent, HYPERV_HEARTBEAT_DRIVER_MODULE_NAME, attributes,
NULL, NULL);
}
static status_t
hyperv_heartbeat_init_driver(device_node* node, void** _driverCookie)
{
CALLED();
Heartbeat* heartbeat = new(std::nothrow) Heartbeat(node);
if (heartbeat == NULL) {
ERROR("Unable to allocate Hyper-V Heartbeat object\n");
return B_NO_MEMORY;
}
status_t status = heartbeat->InitCheck();
if (status != B_OK) {
ERROR("Failed to set up Hyper-V Heartbeat object\n");
delete heartbeat;
return status;
}
TRACE("Hyper-V Heartbeat object created\n");
*_driverCookie = heartbeat;
return B_OK;
}
static void
hyperv_heartbeat_uninit_driver(void* driverCookie)
{
CALLED();
Heartbeat* heartbeat = reinterpret_cast<Heartbeat*>(driverCookie);
delete heartbeat;
}
driver_module_info gHyperVHeartbeatDriverModule = {
{
HYPERV_HEARTBEAT_DRIVER_MODULE_NAME,
0,
NULL
},
hyperv_heartbeat_supports_device,
hyperv_heartbeat_register_device,
hyperv_heartbeat_init_driver,
hyperv_heartbeat_uninit_driver,
NULL, // register child devices
NULL, // rescan
NULL // removed
};
@@ -0,0 +1,46 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HYPERV_HEARTBEAT_PROTOCOL_H_
#define _HYPERV_HEARTBEAT_PROTOCOL_H_
#include "ICProtocol.h"
#define HV_HEARTBEAT_RING_SIZE 0x1000
#define HV_HEARTBEAT_PKT_BUFFER_SIZE 128
// Heartbeat versions
#define HV_HEARTBEAT_VERSION_V1 MAKE_IC_VERSION(1, 0)
#define HV_HEARTBEAT_VERSION_V3 MAKE_IC_VERSION(3, 0)
static const uint32 hv_heartbeat_versions[] = {
HV_HEARTBEAT_VERSION_V3,
HV_HEARTBEAT_VERSION_V1
};
static const uint32 hv_heartbeat_version_count = sizeof(hv_heartbeat_versions)
/ sizeof(hv_heartbeat_versions[0]);
// Heartbeat application states
enum {
HV_HEARTBEAT_APPSTATE_NONE = 0,
HV_HEARTBEAT_APPSTATE_OK = 1,
HV_HEARTBEAT_APPSTATE_CRITICAL = 2
};
// Heartbeat message
typedef struct {
hv_ic_msg_header header;
uint64 sequence;
uint64 app_state;
} _PACKED hv_heartbeat_msg;
#endif // _HYPERV_HEARTBEAT_PROTOCOL_H_
@@ -0,0 +1,11 @@
SubDir HAIKU_TOP src add-ons kernel drivers hyperv hyperv_ic heartbeat ;
UsePrivateKernelHeaders ;
UsePrivateHeaders drivers hyperv ;
SubDirHdrs [ FDirName $(SUBDIR) $(DOTDOT) ] ;
KernelStaticLibrary hyperv_ic_heartbeat :
Heartbeat.cpp
HeartbeatModule.cpp
;