445 lines
10 KiB
PHP
445 lines
10 KiB
PHP
; =============================================================================
|
||
; Модуль : 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
|