title "Cmos Access Routines" ;++ ; ; Module Name: ; ; ixcmos.asm ; ; Abstract: ; ; Procedures necessary to access CMOS/ECMOS information. ; ; Author: ; ; David Risner (o-ncrdr) 20 Apr 1992 ; ; Revision History: ; ; Landy Wang (corollary!landy) 04 Dec 1992 ; - Move much code from ixclock.asm to here so different HALs ; can reuse the common functionality. ; ;-- .386p .xlist include hal386.inc include callconv.inc ; calling convention macros include mac386.inc include i386\ix8259.inc include i386\ixcmos.inc .list EXTRNP _DbgBreakPoint,0,IMPORT extrn _HalpSystemHardwareLock:DWORD extrn _HalpBusType:DWORD extrn _HalpSerialLen:BYTE extrn _HalpSerialNumber:BYTE CMOS_STATUS_BUSY EQU 80H ; Time update in progress RTC_OFFSET_SECOND EQU 0 ; second field of RTC memory RTC_OFFSET_MINUTE EQU 2 ; minute field of RTC memory RTC_OFFSET_HOUR EQU 4 ; hour field of RTC memory RTC_OFFSET_DAY_OF_WEEK EQU 6 ; day-of-week field of RTC memory RTC_OFFSET_DATE_OF_MONTH EQU 7 ; date-of-month field of RTC memory RTC_OFFSET_MONTH EQU 8 ; month field of RTC memory RTC_OFFSET_YEAR EQU 9 ; year field of RTC memory RTC_OFFSET_CENTURY_MCA EQU 37h ; Century field of RTC memory for MCA RTC_OFFSET_CENTURY EQU 32h ; Century field of RTC memory RTC_OFFSET_CENTURY_DS EQU 148h ; Bank 1, 48. Century field for DS BANK1 EQU 100h ; ; BCD_TO_BIN ; ; Description: Convert BCD value to binary ; ; Parameter: ; Input: (AL) = 2 digit BCD number to convert ; Output: (AX) = Binary equivalent (all in AL) ; ; Return: None. ; BCD_TO_BIN macro xor ah,ah rol ax,4 ror al,4 aad endm ; ; BIN_TO_BCD ; ; Description: Convert binary value to BCD. ; ; Parameter: ; Input: (AL) = binary value to be converted. ; Output: (AX) = BCD (all in AL) ; ; Return: None. ; BIN_TO_BCD macro aam rol al, 4 ror ax, 4 endm _DATA SEGMENT DWORD PUBLIC 'DATA' ; ; HalpRebootNow is a reboot vector. Set in an MP system, to ; cause any processors which may be looping in HalpAcquireCmosSinLock ; to transfer control to the vector in HalpRebootNow ; public _HalpRebootNow _HalpRebootNow dd 0 ; ; Holds the value of the eflags register before a cmos spinlock is ; acquired (used in HalpAcquire/ReleaseCmosSpinLock(). ; _HalpHardwareLockFlags dd 0 ; ; Holds the offset to CMOS Century information. ; _HalpCmosCenturyOffset dd 0 _DATA ends subttl "HalpGetCmosData" ;++ ; ; CMOS space read and write functions. ; ;-- CmosAddressPort equ 70H CmosDataPort equ 71H ECmosAddressLsbPort equ 74H ECmosAddressMsbPort equ 75H ECmosDataPort equ 76H INIT SEGMENT DWORD PUBLIC 'CODE' ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING ;++ ; ; VOID ; HalpInitializeCmos( ; VOID ; ) ; ; This routine reads CMOS and initializes globals required for ; CMOS access, such as the location of the century byte. ; ;-- cPublicProc _HalpInitializeCmos,0 push ebx push esi push edi ; ; Assume default ; mov eax, RTC_OFFSET_CENTURY mov _HalpCmosCenturyOffset, eax cmp _HalpBusType, MACHINE_TYPE_ISA jne short icm40 ; ; If control comes here, this is ISA machine. We need to check if this is ; IBM PS/1 or Pc/ValuePoint machine and use RTC_CENTURY_OFFSET_MCA to get ; Century byte from CMOS. ; ; ; Check if the CMOS 2e and 2f contains memory checksum. On PS/1 machine ; the check should fail. ; icm20: mov ecx, 2dh ; from 10h to 2dh mov eax, 0 ; clear ax mov edx, 0 icm30: mov al, cl CMOS_READ add edx, eax dec ecx cmp ecx, 0fh jne short icm30 mov eax, 2eh CMOS_READ mov ah, al mov al, 2fh CMOS_READ cmp eax, edx je short icm50 ; NOT PS/1 mov eax, RTC_OFFSET_CENTURY_MCA mov _HalpCmosCenturyOffset, eax jmp icm90 icm40: cmp _HalpBusType, MACHINE_TYPE_MCA jne short icm50 ; ; See if this is a P700 MCA machine ; in al, 07fh ; get PD700 ID byte and al, 0F0h ; Mask high nibble cmp al, 0A0h ; Is the ID Ax? jz short icm50 cmp al, 090h ; Or an 9X? jz short icm50 ; Yes, it's a 700 mov eax, RTC_OFFSET_CENTURY_MCA mov _HalpCmosCenturyOffset, eax icm50: if 0 - Selecting BANK1 causes some devices to mess up their month value - For now, I'm removing this code until this problem can be solved ; ; See if this is a Dallas Semiconductor DS17285 or later ; Switch to BANK 1 ; mov al, 0Ah CMOS_READ and al, 7fh ; Don't write UIP mov ah, al mov esi, eax ; save it for restore or ah, 10h ; Set DV0 = 1 mov al, 0Ah ; Write register A CMOS_WRITE ; ; Check for RTC serial # with matching crc ; (al) = current byte ; (ah) = scratch register ; (bl) = current crc ; (bh) = zero, non-zero, flag ; (ecx) = cmos offset ; (edx) = used by cmos_read macro ; (esi) = saved register 0A ; mov ecx, 40h xor ebx, ebx icm60: mov al, cl CMOS_READ mov byte ptr _HalpSerialNumber+2+-40h[ecx], al or bh, al ; or to check for all zeros mov ch, 8 ; Bits per byte icm65: mov ah, bl ; ah = crc xor ah, al ; xor LSb shr bl, 1 ; shift crc shr ah, 1 ; mov LSb to carry sbb ah, ah ; if carry set 1's else 0's and ah, (118h shr 1) ; crc polynomial xor bl, ah ; apply it shr al, 1 ; next bit dec ch ; jnz short icm65 ; if ch non-zero, loop inc cl ; next cmos location cmp cl, 48h ; at end? jne short icm60 ; no, loop ; ; (bh) = zero, non-zero flag ; (bl) = crc ; mov eax, RTC_OFFSET_CENTURY_DS ; Read century byte CMOS_READ BCD_TO_BIN movzx ecx, ax ; save it ; ; Switch back to BANK 0 ; mov eax, esi mov al, 0Ah CMOS_WRITE ; ; Check for valid DS data ; cmp bh, 0 ; Was data all zeros? je short icm90 cmp bl, 0 ; was CRC valid? jnz short icm90 cmp ecx, 19 ; Is century before 19? jb short icm90 cmp ecx, 20 ; Is century after 20? ja short icm90 ; ; Setup for DS century byte ; mov byte ptr _HalpSerialNumber+0, 'D' mov byte ptr _HalpSerialNumber+1, 'S' mov _HalpSerialLen, 10 mov eax, RTC_OFFSET_CENTURY_DS mov _HalpCmosCenturyOffset, eax endif icm90: pop edi pop esi pop ebx stdRET _HalpInitializeCmos stdENDP _HalpInitializeCmos INIT ends _TEXT SEGMENT DWORD PUBLIC 'CODE' ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING ;++ ; ; ULONG ; HalpGetCmosData( ; IN ULONG SourceLocation ; IN ULONG SourceAddress ; IN ULONG ReturnBuffer ; IN PUCHAR ByteCount ; ) ; ; This routine reads the requested number of bytes from CMOS/ECMOS and ; stores the data read into the supplied buffer in system memory. If ; the requested data amount exceeds the allowable extent of the source ; location, the return data is truncated. ; ; Arguments: ; ; SourceLocation : where data is to be read from CMOS or ECMOS ; 0 - CMOS, 1 - ECMOS ; ; SourceAddress : address in CMOS/ECMOS where data is to be read from ; ; ReturnBuffer : address in system memory for return data ; ; ByteCount : number of bytes to be read ; ; Returns: ; ; Number of byte actually read. ; ;-- SourceLocation equ 2*4[ebp] SourceAddress equ 3*4[ebp] ReturnBuffer equ 4*4[ebp] ByteCount equ 5*4[ebp] cPublicProc _HalpGetCmosData ,4 push ebp mov ebp, esp push ebx push edi ; ; NOTE: The spinlock is needed even in the UP case, because ; the resource is also used in an interrupt handler (profiler). ; If we own the spinlock in this routine, and we service ; the profiler interrupt (which will wait for the spinlock forever), ; then we have a hosed system. ; stdCall _HalpAcquireCmosSpinLock xor edx, edx ; initialize return data length mov ecx, ByteCount or ecx, ecx ; validate requested byte count jz HalpGetCmosDataExit ; if no work to do, exit mov edx, SourceAddress mov edi, ReturnBuffer mov eax, SourceLocation ; cmos or extended cmos? cmp eax, 1 je ECmosReadByte cmp eax, 0 jne HalpGetCmosDataExit CmosReadByte: cmp edx, 0ffH ; validate cmos source address ja HalpGetCmosDataExit ; if out of range, exit mov al, dl out CmosAddressPort, al in al, CmosDataPort mov [edi], al inc edx inc edi dec ecx jnz CmosReadByte jmp SHORT HalpGetCmosDataExit ECmosReadByte: cmp edx,0ffffH ; validate ecmos source address ja HalpGetCmosDataExit ; if out of range, exit mov al, dl out ECmosAddressLsbPort, al mov al, dh out ECmosAddressMsbPort, al in al, ECmosDataPort mov [edi], al inc edx inc edi dec ecx jnz ECmosReadByte HalpGetCmosDataExit: stdCall _HalpReleaseCmosSpinLock mov eax, edx ; return bytes read pop edi pop ebx pop ebp stdRET _HalpGetCmosData stdENDP _HalpGetCmosData ;++ ; ; VOID ; HalpSetCmosData( ; IN ULONG SourceLocation ; IN ULONG SourceAddress ; IN ULONG ReturnBuffer ; IN PUCHAR ByteCount ; ) ; ; This routine writes the requested number of bytes to CMOS/ECMOS ; ; Arguments: ; ; SourceLocation : where data is to be written to CMOS or ECMOS ; 0 - CMOS, 1 - ECMOS ; ; SourceAddress : address in CMOS/ECMOS where data is to write to. ; ; ReturnBuffer : address in system memory for data to write ; ; ByteCount : number of bytes to be write ; ; Returns: ; ; Number of byte actually written. ; ;-- cPublicProc _HalpSetCmosData ,4 push ebp mov ebp, esp push ebx push edi stdCall _HalpAcquireCmosSpinLock xor edx, edx ; initialize return data length mov ecx, ByteCount or ecx, ecx ; validate requested byte count jz HalpSetCmosDataExit ; if no work to do, exit mov edx, SourceAddress mov edi, ReturnBuffer mov eax, SourceLocation ; cmos or extended cmos? cmp eax, 1 je ECmosWriteByte cmp eax, 0 jne HalpSetCmosDataExit CmosWriteByte: cmp edx, 0ffH ; validate cmos source address ja HalpSetCmosDataExit ; if out of range, exit mov al, dl out CmosAddressPort, al mov al, [edi] out CmosDataPort, al inc edx inc edi dec ecx jnz CmosWriteByte jmp SHORT HalpSetCmosDataExit ECmosWriteByte: cmp edx,0ffffH ; validate ecmos source address ja HalpSetCmosDataExit ; if out of range, exit mov al, dl out ECmosAddressLsbPort, al mov al, dh out ECmosAddressMsbPort, al mov al, [edi] out ECmosDataPort, al inc edx inc edi dec ecx jnz ECmosWriteByte HalpSetCmosDataExit: stdCall _HalpReleaseCmosSpinLock mov eax, edx ; return bytes written pop edi pop ebx pop ebp stdRET _HalpSetCmosData stdENDP _HalpSetCmosData page ,132 subttl "Read System Time" ;++ ; ; VOID ; HalpReadCmosTime ( ; PTIME_FIELDS TimeFields ; ) ; ; Routine Description: ; ; This routine reads current time from CMOS memory and stores it ; in the TIME_FIELDS structure passed in by caller. ; ; Arguments: ; ; TimeFields - A pointer to the TIME_FIELDS structure. ; ; Return Value: ; ; None. ; ;-- ; ; Parameters: ; KrctPTimeFields equ [esp+4] cPublicProc _HalpReadCmosTime ,1 if DBG krctwait0: mov ecx, 100 krctwait: push ecx else krctwait: endif stdCall _HalpAcquireCmosSpinLock mov ecx, 100 align 4 krct00: mov al, 0Ah ; Specify register A CMOS_READ ; (al) = CMOS register A test al, CMOS_STATUS_BUSY ; Is time update in progress? jz short krct10 ; if z, no, go read CMOS time loop short krct00 ; otherwise, try again. ; ; CMOS is still busy. Try again ... ; stdCall _HalpReleaseCmosSpinLock if DBG pop ecx loop short krctwait stdCall _DbgBreakPoint jmp short krctwait0 else jmp short krctwait endif align 4 if DBG krct10: pop ecx else krct10: endif mov edx, KrctPTimeFields ; (edx)-> TIME_FIELDS structure xor eax, eax ; (eax) = 0 mov al, RTC_OFFSET_SECOND CMOS_READ ; (al) = second in BCD form BCD_TO_BIN ; (ax) = second mov [edx].TfSecond, ax ; set second in TIME_FIELDS mov al, RTC_OFFSET_MINUTE CMOS_READ ; (al) = minute in BCD form BCD_TO_BIN ; (ax) = Minute mov [edx].TfMinute, ax ; set minute in TIME_FIELDS mov al, RTC_OFFSET_HOUR CMOS_READ ; (al) = hour in BCD form BCD_TO_BIN ; (ax) = Hour mov [edx].TfHour, ax ; set hour in TIME_FIELDS mov al, RTC_OFFSET_DAY_OF_WEEK CMOS_READ ; (al) = day-of-week in BCD form BCD_TO_BIN ; (ax) = day-of-week mov [edx].TfWeekday, ax ; set Weekday in TIME_FIELDS mov al, RTC_OFFSET_DATE_OF_MONTH CMOS_READ ; (al) = date-of-month in BCD form BCD_TO_BIN ; (ax) = date_of_month mov [edx].TfDay, ax ; set day in TIME_FIELDS mov al, RTC_OFFSET_MONTH CMOS_READ ; (al) = month in BCD form BCD_TO_BIN ; (ax) = month mov [edx].TfMonth, ax ; set month in TIME_FIELDS mov al, RTC_OFFSET_YEAR CMOS_READ ; (al) = year in BCD form BCD_TO_BIN ; (ax) = year push eax ; save year in stack push edx ; preserve edx call _HalpGetCmosCenturyByte ; (al)= century byte in BCD form BCD_TO_BIN ; (ax) = century pop edx mov ah, 100 mul ah ; (ax) = century * 100 pop ecx ; (cx) = year add ax, cx ; (ax)= year cmp ax, 1900 ; Is year > 1900 jb short krct40 cmp ax, 1920 ; and < 1920 jae short krct40 add ax, 100 ; Compensate for century field krct40: mov [edx].TfYear, ax ; set year in TIME_FIELDS mov word ptr [edx].TfMilliseconds, 0 ; do not support stdCall _HalpReleaseCmosSpinLock stdRET _HalpReadCmosTime stdENDP _HalpReadCmosTime page ,132 subttl "Write System Time" ;++ ; ; VOID ; HalpWriteCmosTime ( ; PTIME_FIELDS TimeFields ; ) ; ; Routine Description: ; ; This routine writes current time from TIME_FILEDS structure ; to CMOS memory. ; ; Arguments: ; ; TimeFields - A pointer to the TIME_FIELDS structure. ; ; Return Value: ; ; None. ; ;-- ; ; Parameters: ; KrctPTimeFields equ [esp+4] cPublicProc _HalpWriteCmosTime ,1 if DBG kwctwait0: mov ecx, 100 kwctwait: push ecx else kwctwait: endif stdCall _HalpAcquireCmosSpinLock mov ecx, 100 align 4 kwct00: mov al, 0Ah ; Specify register A CMOS_READ ; (al) = CMOS register A test al, CMOS_STATUS_BUSY ; Is time update in progress? jz short kwct10 ; if z, no, go write CMOS time loop short kwct00 ; otherwise, try again. ; ; CMOS is still busy. Try again ... ; stdCall _HalpReleaseCmosSpinLock if DBG pop ecx loop short kwctwait stdCall _DbgBreakPoint jmp short kwctwait0 else jmp short kwctwait endif align 4 if DBG kwct10: pop ecx else kwct10: endif mov edx, KrctPTimeFields ; (edx)-> TIME_FIELDS structure mov al, [edx].TfSecond ; Read second in TIME_FIELDS BIN_TO_BCD mov ah, al mov al, RTC_OFFSET_SECOND CMOS_WRITE mov al, [edx].TfMinute ; Read minute in TIME_FIELDS BIN_TO_BCD mov ah, al mov al, RTC_OFFSET_MINUTE CMOS_WRITE mov al, [edx].TfHour ; Read Hour in TIME_FIELDS BIN_TO_BCD mov ah, al mov al, RTC_OFFSET_HOUR CMOS_WRITE mov al, [edx].TfWeekDay ; Read WeekDay in TIME_FIELDS BIN_TO_BCD mov ah, al mov al, RTC_OFFSET_DAY_OF_WEEK CMOS_WRITE mov al, [edx].TfDay ; Read day in TIME_FIELDS BIN_TO_BCD mov ah, al mov al, RTC_OFFSET_DATE_OF_MONTH CMOS_WRITE mov al, [edx].TfMonth ; Read month in TIME_FIELDS BIN_TO_BCD mov ah, al mov al, RTC_OFFSET_MONTH CMOS_WRITE mov ax, [edx].TfYear ; Read Year in TIME_FIELDS cmp ax, 9999 jbe short kwct15 mov ax, 9999 align 4 kwct15: mov cl, 100 div cl ; [ax]/[cl]->al=quo, ah=rem push eax BIN_TO_BCD push eax call _HalpSetCmosCenturyByte pop eax mov al, ah ; [al] = Year BIN_TO_BCD mov ah, al ; [ah] = year in BCD mov al, RTC_OFFSET_YEAR CMOS_WRITE stdCall _HalpReleaseCmosSpinLock stdRET _HalpWriteCmosTime stdENDP _HalpWriteCmosTime ;++ ; ; Routine Description: ; ; Acquires a spinlock to access the cmos chip. The cmos chip is ; accessed at different irql levels, so to be safe, we 'cli'. ; We could replace that to raise irql to PROFILE_LEVEL, but that's ; a lot of code. ; ; Arguments: ; ; None ; ; Return Value: ; ; Interrupt is disabled. ; Irql level not affected. ; Flags saved in _HalpHardwareLockFlags. ;-- cPublicProc _HalpAcquireCmosSpinLock ,0 push eax Arsl10: pushfd cli lea eax, _HalpSystemHardwareLock ACQUIRE_SPINLOCK eax, Arsl20 pop _HalpHardwareLockFlags ; save flags for release S.L. pop eax stdRET _HalpAcquireCmosSpinLock Arsl20: popfd Arsl30: ifndef NT_UP cmp _HalpRebootNow, 0 jnz short Arsl50 endif TEST_SPINLOCK eax, jmp short ARsl10 Arsl50: ifndef NT_UP mov eax, _HalpRebootNow call eax int 3 ; should not return endif stdENDP _HalpAcquireCmosSpinLock ;++ ; ; Routine Description: ; ; Release spinlock, and restore flags to the state it was before ; acquiring the spinlock. ; ; Arguments: ; ; None ; ; Return Value: ; ; Interrupts restored to their state before acquiring spinlock. ; Irql level not affected. ; ;-- cPublicProc _HalpReleaseCmosSpinLock ,0 push eax ; ; restore eflags as it was before acquiring spinlock. Put it on ; stack before releasing spinlock (so other cpus cannot overwrite ; it with their own eflags). ; push _HalpHardwareLockFlags ; old eflags on stack. lea eax, _HalpSystemHardwareLock RELEASE_SPINLOCK eax popfd ; restore eflags. pop eax stdRET _HalpReleaseCmosSpinLock stdENDP _HalpReleaseCmosSpinLock ;++ ; ; UCHAR ; HalpGetCmosCenturyByte ( ; VOID ; ) ; ; Routine Description: ; ; This routine gets Century byte from CMOS. ; ; Arguments: ; ; None ; ; Return Value: ; ; (al) = Century byte in BCD form. ; ;-- cPublicProc _HalpGetCmosCenturyByte, 0 mov eax, _HalpCmosCenturyOffset if DBG ; ; Make sure the HalpCmosCenturyOffset is initialized ; cmp eax, 0 jne short @f int 3 @@: endif test eax, BANK1 jnz short rcb50 CMOS_READ ; (al) = century in BCD form stdRET _HalpGetCmosCenturyByte rcb50: mov edx, eax mov al, 0Ah CMOS_READ mov dh, al ; save it for restore or al, 10h ; Set DV0 = 1 mov ah, al mov al, 0Ah ; Write register A CMOS_WRITE mov al, dl ; century offset CMOS_READ mov dl, al ; save it mov ah, dh ; Restore DV0 mov al, 0Ah ; Write register A CMOS_WRITE mov al, dl stdRET _HalpGetCmosCenturyByte stdENDP _HalpGetCmosCenturyByte ;++ ; ; VOID ; HalpSetCmosCenturyByte ( ; UCHAR Century ; ) ; ; Routine Description: ; ; This routine sets Century byte in CMOS. ; ; Arguments: ; ; Century - Supplies the value for CMOS century byte ; ; Return Value: ; ; None. ; ;-- cPublicProc _HalpSetCmosCenturyByte, 1 mov eax, _HalpCmosCenturyOffset if DBG ; ; Make sure the HalpCmosCenturyOffset is initialized ; cmp eax, 0 jne short @f int 3 @@: endif test eax, BANK1 jnz short scb50 mov ah, [esp+4] ; (ah) = Century in BCD form CMOS_WRITE stdRET _HalpSetCmosCenturyByte scb50: mov edx, eax mov al, 0Ah CMOS_READ mov dh, al ; save it for restore or al, 10h ; Set DV0 = 1 mov ah, al mov al, 0Ah ; Write register A CMOS_WRITE mov ah, [esp+4] ; (ah) = Century in BCD form mov al, dl ; century offset CMOS_WRITE mov ah, dh ; Restore DV0 mov al, 0Ah ; Write register A CMOS_WRITE stdRET _HalpSetCmosCenturyByte stdENDP _HalpSetCmosCenturyByte ;++ ; ; VOID ; HalpCpuID ( ; ULONG InEax, ; PULONG OutEax, ; PULONG OutEbx, ; PULONG OutEcx, ; PULONG OutEdx ; ); ; ; Routine Description: ; ; Executes the CPUID instruction and returns the registers from it ; ; Only available at INIT time ; ; Arguments: ; ; Return Value: ; ;-- cPublicProc _HalpCpuID,5 push ebx push esi mov eax, [esp+12] db 0fh, 0a2h ; CPUID mov esi, [esp+16] ; return EAX mov [esi], eax mov esi, [esp+20] ; return EBX mov [esi], ebx mov esi, [esp+24] ; return ECX mov [esi], ecx mov esi, [esp+28] ; return EDX mov [esi], edx pop esi pop ebx stdRET _HalpCpuID stdENDP _HalpCpuID ;++ ; ; VOID ; HalpFlushTLB ( ; VOID ; ); ; ; Routine Description: ; ; Flush the current TLB. ; ; Arguments: ; ; Return Value: ; ;-- cPublicProc _HalpFlushTLB, 0 .586p pushfd push ebx push esi cli mov esi, cr3 mov ecx, PCR[PcPrcb] cmp byte ptr [ecx].PbCpuID, 0 jz short ftb50 mov eax, 1 ; Get feature bits cpuid ; (note "cpuid" between CR3 reload fixes ; P6 B step errata #11) test edx, 2000h ; see if 'G' bit is supported jz short ftb50 mov ecx, cr4 ; 'G' bit is supported, due global flush mov edx, ecx ; Save orginal cr4 and ecx, not CR4_PGE ; Make sure global bit is disabled mov cr4, ecx mov cr3, esi ; flush TLB mov cr4, edx ; restore cr4 jmp short ftp99 ftb50: mov cr3, esi ftp99: pop esi pop ebx popfd stdRET _HalpFlushTLB .486p stdENDP _HalpFlushTLB _TEXT ends end