Files
VBoxGuest/services/timesync/timesync.inc
2026-03-04 22:03:47 +03:00

445 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
; =============================================================================
; Модуль : VMMDev Time Sync Service
; Назначение : Синхронизация часов гостевой ОС с хостом VirtualBox
; Файл : services/timesync/timesync.inc
;
; VBox возвращает 64-bit миллисекунды с Unix epoch (1970-01-01).
; Драйвер пишет время напрямую в CMOS RTC (порты 0x70/0x71).
; int 0x40 нельзя из kernel-mode: обработчик syscall предполагает ring 3
; (переключение стеков user→kernel), из ring 0 это разрушает стек.
; =============================================================================
svc_timesync_name db "TIMESYNC", 0
; --- Data ---
align 4
vbox_timesync_enabled dd 0
vbox_timesync_last_tick dd 0
align 4
vmmdev_get_host_time_s VMMDEV_GET_HOST_TIME \
<sizeof.VMMDEV_GET_HOST_TIME, \
VMMDEV_REQUEST_HEADER_VERSION, \
VMMDEV_REQ_GET_HOST_TIME, \
0, 0, 0>, \
0, 0
align 4
timesync_time_virt dd 0
timesync_time_phys dd 0
; CMOS port constants
CMOS_ADDR equ 0x70
CMOS_DATA equ 0x71
; timesync_init Инициализация
proc timesync_init
DEBUGF 2, "[VBoxGuest] [TimeSync] Initializing...\n"
mov eax, vmmdev_get_host_time_s
mov [timesync_time_virt], eax
invoke GetPhysAddr
mov [timesync_time_phys], eax
DEBUGF 2, "[VBoxGuest] [TimeSync] Buffer: virt=0x%x, phys=0x%x\n", \
[timesync_time_virt], [timesync_time_phys]
mov dword [vbox_timesync_enabled], 1
call timesync_do_sync
DEBUGF 2, "[VBoxGuest] [TimeSync] Initialized OK\n"
xor eax, eax
ret
endp
; timesync_tick Проверка интервала и синхронизация
proc timesync_tick
cmp dword [vbox_timesync_enabled], 0
je .done
invoke GetTimerTicks
sub eax, [vbox_timesync_last_tick]
cmp eax, TIMESYNC_INTERVAL_TICKS
jb .done
invoke GetTimerTicks
mov [vbox_timesync_last_tick], eax
call timesync_do_sync
.done:
ret
endp
; timesync_do_sync Получить время хоста и записать в CMOS RTC
proc timesync_do_sync uses ebx ecx edx esi edi
DEBUGF 2, "[VBoxGuest] [TimeSync] Syncing...\n"
mov edi, [timesync_time_virt]
test edi, edi
jz .bad_ptr
mov dword [edi + VMMDEV_GET_HOST_TIME.header.rc], 0
mov dword [edi + VMMDEV_GET_HOST_TIME.time_low], 0
mov dword [edi + VMMDEV_GET_HOST_TIME.time_high], 0
stdcall vmmdev_send_request, [timesync_time_phys]
mov eax, [edi + VMMDEV_GET_HOST_TIME.header.rc]
test eax, eax
js .error
; 64-bit миллисекунды с epoch
mov eax, [edi + VMMDEV_GET_HOST_TIME.time_low]
mov edx, [edi + VMMDEV_GET_HOST_TIME.time_high]
DEBUGF 2, "[VBoxGuest] [TimeSync] Host ms: hi=0x%x lo=0x%x\n", edx, eax
mov ecx, eax
or ecx, edx
jz .bad_time
; edx:eax / 1000 eax = unix seconds
mov ecx, 1000
div ecx
DEBUGF 2, "[VBoxGuest] [TimeSync] Unix timestamp: %d\n", eax
call timesync_set_cmos
DEBUGF 2, "[VBoxGuest] [TimeSync] Sync complete\n"
xor eax, eax
ret
.bad_ptr:
DEBUGF 2, "[VBoxGuest] [TimeSync] Bad pointer\n"
mov eax, VERR_INVALID_POINTER
ret
.error:
DEBUGF 2, "[VBoxGuest] [TimeSync] GetHostTime failed: rc=0x%x\n", eax
ret
.bad_time:
DEBUGF 2, "[VBoxGuest] [TimeSync] Zero time returned\n"
mov eax, VERR_GENERAL_FAILURE
ret
endp
; timesync_set_cmos Вычисляет дату/время и записывает в CMOS RTC
proc timesync_set_cmos uses ebx ecx edx esi edi
locals
ts_sec dd ?
ts_min dd ?
ts_hour dd ?
ts_day dd ?
ts_month dd ?
ts_year dd ?
ts_dow dd ?
endl
; ── Разложить на дни и время дня ──
xor edx, edx
mov ecx, 86400
div ecx
; eax = total_days, edx = time_of_day_seconds
mov esi, eax ; total_days
; Время суток
mov eax, edx
xor edx, edx
mov ecx, 3600
div ecx
mov [ts_hour], eax
mov eax, edx
xor edx, edx
mov ecx, 60
div ecx
mov [ts_min], eax
mov [ts_sec], edx
; ── День недели: (days + 3) % 7 + 1 ──
lea eax, [esi + 3]
xor edx, edx
mov ecx, 7
div ecx
inc edx
mov [ts_dow], edx
; ── civil_from_days (Howard Hinnant) ──
; eax = days дата
mov eax, esi
call .civil_from_days
mov [ts_day], eax
mov [ts_month], ebx
mov [ts_year], ecx
DEBUGF 2, "[VBoxGuest] [TimeSync] Date: %d-%d-%d %d:%d:%d DoW=%d\n", \
[ts_year], [ts_month], [ts_day], [ts_hour], [ts_min], [ts_sec], [ts_dow]
; ── Ждать окончания обновления RTC ──
call .cmos_wait
; ── Запретить обновления (SET bit в Status B) ──
mov al, 0x0B
out CMOS_ADDR, al
in al, CMOS_DATA
or al, 0x80
push eax
mov al, 0x0B
out CMOS_ADDR, al
pop eax
out CMOS_DATA, al
; ── Записать все поля (BCD) ──
mov eax, [ts_sec]
call .to_bcd
mov ah, al
mov al, 0x00 ; seconds register
call .cmos_wr
mov eax, [ts_min]
call .to_bcd
mov ah, al
mov al, 0x02 ; minutes register
call .cmos_wr
mov eax, [ts_hour]
call .to_bcd
mov ah, al
mov al, 0x04 ; hours register
call .cmos_wr
mov eax, [ts_dow]
mov ah, al
mov al, 0x06 ; weekday register
call .cmos_wr
mov eax, [ts_day]
call .to_bcd
mov ah, al
mov al, 0x07 ; day register
call .cmos_wr
mov eax, [ts_month]
call .to_bcd
mov ah, al
mov al, 0x08 ; month register
call .cmos_wr
; YY = year mod 100
mov eax, [ts_year]
xor edx, edx
mov ecx, 100
div ecx
push eax ; century
mov eax, edx
call .to_bcd
mov ah, al
mov al, 0x09 ; year register
call .cmos_wr
; Century
pop eax
call .to_bcd
mov ah, al
mov al, 0x32 ; century register
call .cmos_wr
; ── Разрешить обновления ──
mov al, 0x0B
out CMOS_ADDR, al
in al, CMOS_DATA
and al, 0x7F
push eax
mov al, 0x0B
out CMOS_ADDR, al
pop eax
out CMOS_DATA, al
ret
; ── Helpers ──
.cmos_wr:
; al = register, ah = value
out CMOS_ADDR, al
mov al, ah
out CMOS_DATA, al
ret
.cmos_wait:
push eax ecx
mov ecx, 10000
@@:
mov al, 0x0A
out CMOS_ADDR, al
in al, CMOS_DATA
test al, 0x80
jz @f
pause
loop @b
@@:
pop ecx eax
ret
.to_bcd:
; eax (0-99) eax BCD
push ecx edx
and eax, 0xFF
xor edx, edx
mov ecx, 10
div ecx
shl eax, 4
or eax, edx
pop edx ecx
ret
; .civil_from_days Howard Hinnant algorithm
.civil_from_days:
; z = days + 719468
add eax, 719468
; era = z / 146097
xor edx, edx
mov ecx, 146097
div ecx
mov esi, eax ; era
; doe = z - era * 146097 (но z уже потерян используем edx = remainder)
mov ecx, edx ; ecx = doe
; yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365
mov eax, ecx
xor edx, edx
mov edi, 1460
div edi
mov edi, eax ; doe/1460
mov eax, ecx
xor edx, edx
push ecx
mov ecx, 36524
div ecx
pop ecx
sub edi, eax ; doe/1460 - doe/36524 (note: we subtract then add later)
; Actually: val = doe - doe/1460 + doe/36524 - doe/146096
; Let me redo properly:
; A = doe/1460
push ecx
mov eax, ecx
xor edx, edx
mov ecx, 1460
div ecx
pop ecx
push eax ; [esp] = A
; B = doe/36524
push ecx
mov eax, ecx
xor edx, edx
mov ecx, 36524
div ecx
pop ecx
push eax ; [esp]=B, [esp+4]=A
; C = doe/146096
push ecx
mov eax, ecx
xor edx, edx
mov ecx, 146096
div ecx
pop ecx
; eax = C
pop ebx ; B
pop edi ; A
; val = doe - A + B - C
mov edx, ecx ; doe
sub edx, edi
add edx, ebx
sub edx, eax
; yoe = val / 365
mov eax, edx
xor edx, edx
push ecx
mov ecx, 365
div ecx
pop ecx
; eax = yoe, ecx still = doe
mov edi, eax ; edi = yoe
; year = yoe + era * 400
mov eax, esi ; era
imul eax, 400
add eax, edi
push eax ; [esp] = year
; doy = doe - (365*yoe + yoe/4 - yoe/100)
mov eax, edi
imul eax, 365
mov esi, eax ; 365*yoe
mov eax, edi
shr eax, 2
add esi, eax ; + yoe/4
mov eax, edi
xor edx, edx
push ecx
mov ecx, 100
div ecx
pop ecx
sub esi, eax ; - yoe/100
sub ecx, esi ; ecx = doy
; mp = (5*doy + 2) / 153
imul eax, ecx, 5
add eax, 2
xor edx, edx
push ecx
mov ecx, 153
div ecx
pop ecx
mov edi, eax ; mp
; day = doy - (153*mp + 2)/5 + 1
imul eax, edi, 153
add eax, 2
xor edx, edx
push ecx
mov ecx, 5
div ecx
pop ecx
sub ecx, eax
inc ecx ; ecx = day
; month = mp < 10 ? mp + 3 : mp - 9
cmp edi, 10
jae .mp_ge10
add edi, 3
jmp .month_done2
.mp_ge10:
sub edi, 9
.month_done2:
; edi = month, ecx = day
; if month <= 2 year++
pop eax ; year
cmp edi, 2
ja .year_ok2
inc eax
.year_ok2:
; Формируем результат: eax=day, ebx=month, ecx=year
mov ebx, edi ; month
xchg eax, ecx ; eax=day, ecx=year
ret
endp
; timesync_disable Выключить timesync
proc timesync_disable
DEBUGF 2, "[VBoxGuest] [TimeSync] Disabling...\n"
mov dword [vbox_timesync_enabled], 0
xor eax, eax
ret
endp
REGISTER_SERVICE svc_timesync_name, TIMESYNC_EVENT_MASK, 0, \
timesync_init, 0, timesync_disable, 0, timesync_tick, AUTOSTART_TIMESYNC