hyperv_ic: Add time sync support

Adds support for the time sync Hyper-V integration component.

Change-Id: If8617feab196e59b8e1d2447ffbcb344a0e66f38
Reviewed-on: https://review.haiku-os.org/c/haiku/+/10503
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: Jérôme Duval <[email protected]>
This commit is contained in:
John Davis
2026-03-17 06:25:53 +00:00
committed by Jérôme Duval
parent 4f6e4b41ba
commit 640b37b32d
10 changed files with 313 additions and 6 deletions
@@ -25,13 +25,13 @@ ICBase::ICBase(device_node* node, uint32 packetLength, uint16 messageType,
fNode(node),
fFrameworkVersion(0),
fMessageVersion(0),
fHyperV(NULL),
fHyperVCookie(NULL),
fPacket(NULL),
fPacketLength(packetLength),
fMessageType(messageType),
fMessageVersions(messageVersions),
fMessageVersionCount(messageVersionCount),
fHyperV(NULL),
fHyperVCookie(NULL)
fMessageVersionCount(messageVersionCount)
{
CALLED();
@@ -39,6 +39,9 @@ protected:
uint32 fFrameworkVersion;
uint32 fMessageVersion;
hyperv_device_interface* fHyperV;
hyperv_device fHyperVCookie;
private:
status_t _NegotiateProtocol(hv_ic_msg_negotiate* message);
static void _CallbackHandler(void* arg);
@@ -50,9 +53,6 @@ private:
uint16 fMessageType;
const uint32* fMessageVersions;
uint32 fMessageVersionCount;
hyperv_device_interface* fHyperV;
hyperv_device fHyperVCookie;
};
@@ -18,5 +18,6 @@ module_dependency module_dependencies[] = {
module_info* modules[] = {
(module_info*)&gHyperVHeartbeatDriverModule,
(module_info*)&gHyperVTimeSyncDriverModule,
NULL
};
@@ -13,6 +13,7 @@
extern device_manager_info* gDeviceManager;
extern driver_module_info gHyperVHeartbeatDriverModule;
extern driver_module_info gHyperVTimeSyncDriverModule;
#endif // _HYPERV_IC_DRIVER_H_
@@ -9,6 +9,8 @@ KernelAddon hyperv_ic :
:
hyperv_ic_heartbeat.a
hyperv_ic_timesync.a
;
SubInclude HAIKU_TOP src add-ons kernel drivers hyperv hyperv_ic heartbeat ;
SubInclude HAIKU_TOP src add-ons kernel drivers hyperv hyperv_ic timesync ;
@@ -0,0 +1,11 @@
SubDir HAIKU_TOP src add-ons kernel drivers hyperv hyperv_ic timesync ;
UsePrivateKernelHeaders ;
UsePrivateHeaders drivers hyperv ;
SubDirHdrs [ FDirName $(SUBDIR) $(DOTDOT) ] ;
KernelStaticLibrary hyperv_ic_timesync :
TimeSync.cpp
TimeSyncModule.cpp
;
@@ -0,0 +1,90 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include <real_time_clock.h>
#include "TimeSync.h"
TimeSync::TimeSync(device_node* node)
: ICBase(node, HV_TIMESYNC_PKT_BUFFER_SIZE, HV_IC_MSGTYPE_TIMESYNC, hv_timesync_versions,
hv_timesync_version_count)
{
CALLED();
fStatus = Connect(HV_TIMESYNC_RING_SIZE, HV_TIMESYNC_RING_SIZE);
}
void
TimeSync::OnProtocolNegotiated()
{
TRACE("Time sync protocol version %u.%u\n", GET_IC_VERSION_MAJOR(fMessageVersion),
GET_IC_VERSION_MINOR(fMessageVersion));
}
void
TimeSync::OnMessageReceived(hv_ic_msg* icMessage)
{
if (fMessageVersion >= HV_TIMESYNC_VERSION_V4_0) {
hv_timesync_msg_v4* message = reinterpret_cast<hv_timesync_msg_v4*>(icMessage);
if (message->header.data_length < offsetof(hv_timesync_msg_v4, stratum)
- sizeof(message->header)) {
ERROR("Time sync msg invalid length 0x%X\n", message->header.data_length);
message->header.status = HV_IC_STATUS_FAILED;
return;
}
_SyncTime(message->parent_time, message->reference_time, message->flags);
} else {
hv_timesync_msg_v1* message = reinterpret_cast<hv_timesync_msg_v1*>(icMessage);
if (message->header.data_length < offsetof(hv_timesync_msg_v1, flags)
- sizeof(message->header)) {
ERROR("Time sync msg invalid length 0x%X\n", message->header.data_length);
message->header.status = HV_IC_STATUS_FAILED;
return;
}
_SyncTime(message->parent_time, 0, message->flags);
}
}
void
TimeSync::_SyncTime(uint64 hostTime, uint64 referenceTime, uint8 flags)
{
TRACE("New host time %" B_PRIu64 " ref time %" B_PRIu64 " flags 0x%X\n", hostTime,
referenceTime, flags);
if ((flags & (HV_TIMESYNC_MSGFLAGS_SYNC | HV_TIMESYNC_MSGFLAGS_SAMPLE)) == 0)
return;
// Calculate adjustment based on reference counter if supported
uint64 timeAdjust = 0;
if (referenceTime != 0) {
uint64 refCount;
if (fHyperV->get_reference_counter(fHyperVCookie, &refCount) == B_OK) {
timeAdjust = refCount - referenceTime;
TRACE("Reference counter adjustment by %" B_PRIu64 "\n", timeAdjust);
}
}
// Time provided by Hyper-V is always UTC
bigtime_t newTime = (hostTime - HV_TIMESYNC_TIME_BASE + timeAdjust) / 10LL;
TRACE("New time %" B_PRIdBIGTIME "\n", newTime);
if ((flags & HV_TIMESYNC_MSGFLAGS_SAMPLE) != 0) {
bigtime_t currentTime = real_time_clock_usecs();
TRACE("Current time %" B_PRIdBIGTIME "\n", currentTime);
bigtime_t diffTime = (newTime > currentTime) ? (newTime - currentTime)
: (currentTime - newTime);
TRACE("Time diff %" B_PRIdBIGTIME "\n", diffTime);
if (diffTime <= HV_MS_TO_US(500))
return;
}
// System time will be changed if explicitly requested, or the difference is too great
TRACE("Set system time to %" B_PRIdBIGTIME "\n", newTime);
set_real_time_clock_usecs(newTime);
}
@@ -0,0 +1,40 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HYPERV_TIMESYNC_H_
#define _HYPERV_TIMESYNC_H_
#include "ICBase.h"
#include "TimeSyncProtocol.h"
//#define TRACE_HYPERV_TIMESYNC
#ifdef TRACE_HYPERV_TIMESYNC
# define TRACE(x...) dprintf("\33[94mhyperv_timesync:\33[0m " x)
#else
# define TRACE(x...) ;
#endif
#define TRACE_ALWAYS(x...) dprintf("\33[94mhyperv_timesync:\33[0m " x)
#define ERROR(x...) dprintf("\33[94mhyperv_timesync:\33[0m " x)
#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
#define HYPERV_TIMESYNC_DRIVER_MODULE_NAME "drivers/hyperv/hyperv_ic/timesync/driver_v1"
class TimeSync : public ICBase {
public:
TimeSync(device_node* node);
protected:
void OnProtocolNegotiated();
void OnMessageReceived(hv_ic_msg* icMessage);
private:
void _SyncTime(uint64 hostTime, uint64 referenceTime, uint8 flags);
};
#endif // _HYPERV_TIMESYNC_H_
@@ -0,0 +1,100 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "TimeSync.h"
static float
hyperv_timesync_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 time sync 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_TIMESYNC) != 0)
return 0.0f;
TRACE("Hyper-V Time Sync device found!\n");
return 0.8f;
}
static status_t
hyperv_timesync_register_device(device_node* parent)
{
CALLED();
device_attr attributes[] = {
{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE,
{ .string = HYPERV_PRETTYNAME_TIMESYNC }},
{ NULL }
};
return gDeviceManager->register_node(parent, HYPERV_TIMESYNC_DRIVER_MODULE_NAME, attributes,
NULL, NULL);
}
static status_t
hyperv_timesync_init_driver(device_node* node, void** _driverCookie)
{
CALLED();
TimeSync* timeSync = new(std::nothrow) TimeSync(node);
if (timeSync == NULL) {
ERROR("Unable to allocate Hyper-V Time Sync object\n");
return B_NO_MEMORY;
}
status_t status = timeSync->InitCheck();
if (status != B_OK) {
ERROR("Failed to set up Hyper-V Time Sync object\n");
delete timeSync;
return status;
}
TRACE("Hyper-V Time Sync object created\n");
*_driverCookie = timeSync;
return B_OK;
}
static void
hyperv_timesync_uninit_driver(void* driverCookie)
{
CALLED();
TimeSync* timeSync = reinterpret_cast<TimeSync*>(driverCookie);
delete timeSync;
}
driver_module_info gHyperVTimeSyncDriverModule = {
{
HYPERV_TIMESYNC_DRIVER_MODULE_NAME,
0,
NULL
},
hyperv_timesync_supports_device,
hyperv_timesync_register_device,
hyperv_timesync_init_driver,
hyperv_timesync_uninit_driver,
NULL, // register child devices
NULL, // rescan
NULL // removed
};
@@ -0,0 +1,62 @@
/*
* Copyright 2026 John Davis. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _HYPERV_TIMESYNC_PROTOCOL_H_
#define _HYPERV_TIMESYNC_PROTOCOL_H_
#include "ICProtocol.h"
#define HV_TIMESYNC_RING_SIZE 0x1000
#define HV_TIMESYNC_PKT_BUFFER_SIZE 128
// Time sync versions
#define HV_TIMESYNC_VERSION_V1_0 MAKE_IC_VERSION(1, 0)
#define HV_TIMESYNC_VERSION_V3_0 MAKE_IC_VERSION(3, 0)
#define HV_TIMESYNC_VERSION_V4_0 MAKE_IC_VERSION(4, 0)
static const uint32 hv_timesync_versions[] = {
HV_TIMESYNC_VERSION_V4_0,
HV_TIMESYNC_VERSION_V3_0,
HV_TIMESYNC_VERSION_V1_0
};
static const uint32 hv_timesync_version_count = sizeof(hv_timesync_versions)
/ sizeof(hv_timesync_versions[0]);
#define HV_TIMESYNC_TIME_BASE 116444736000000000ULL
// Time sync message flags
#define HV_TIMESYNC_MSGFLAGS_SYNC (1 << 0)
#define HV_TIMESYNC_MSGFLAGS_SAMPLE (1 << 1)
// Time sync message (versions 1 and 3)
typedef struct {
hv_ic_msg_header header;
uint64 parent_time;
uint64 child_time;
uint64 round_trip_time;
uint8 flags;
} _PACKED hv_timesync_msg_v1;
// Time sync message (version 4)
typedef struct {
hv_ic_msg_header header;
uint64 parent_time;
uint64 reference_time;
uint8 flags;
uint8 leap_flags;
uint8 stratum;
uint8 reserved[3];
} _PACKED hv_timesync_msg_v4;
#endif