;'[T Detect Virtual Machine Type]
;;_______________________________________________________________________________

;'This code attempts to detect if the OS is running under a virtual machine,
;'like Bochs, Qemu, VirtualPC, VMWare, etc.[n]
;'Notes:[n]
;'VirtualBox is currently misreported as Bochs. I haven't found a way to tell
;'these emulators apart (yet).[n]
;'* Some emulators (Parallels, PearPC) may be reported as other emulators or
;'not detected at all. I haven't been able to test these emulators.[n]
;'* Hyper-visors (using hardware virtualization) are not detected at all
;'(Linux KVM, Xen, etc).[n]
;'* I'm not too sure about some of the more exotic virtual machines (SimNow,
;'Simics, Virtual Iron, Virtual Server).[n]

	MARK

detectVM:
	call .checkBochs
	jc .found
	call .checkVirtualPC
	jc .found
	call .checkVMware
	jc .found
	call .checkQemu
	jc .found
	call clearInvalidOpcodeException			;Remove the invalid opcode exception handling
	ret

.found:
	mov [BOOTMEMsystemDataBlock+SDB.virtualMachine],al
	call clearInvalidOpcodeException			;Remove the invalid opcode exception handling
	mov ebx,VMfoundString
	call LOGaddString
	mov ebx,edx
	call LOGaddString
	call LOGflush
	ret


;'[t Check For Bochs]
;;_______________________________________________________________________________

;'The easiest way to detect Bochs is to use instruction timings, as an extremely
;'expensive operation (like [iMUL] or [iDIV] with a cache miss) will only take
;'1 cycle, while on any real computer it'd take a lot more. This method works
;'reliably unless the [iRDTSC] (and/or [iWBINVD]) instruction isn't supported.[n]
;'For 80486, [iRDTSC] isn't supported. In this case we use the PIT timer IRQ
;'and see how many times a loop can execute with and without flushing caches. The
;'loop that does flush caches shouldn't execute as many times as the loop that
;'does flush caches.[n]

.checkBochs:

;Do a quick pre-check

	in al,0xE9						;Get return value from Bochs debug port
	cmp al,0xE9						;Is Bochs running (and compiled with "./configure --enable-port-e9-hack")?
	je .isBochs						; yes, running in Bochs

;Setup the invalid opcode exception handler

	mov ebx,.BOCHSinvOpcode00
	call setInvalidOpcodeException

;Check the instruction timings

	cli
	CPU 586
	rdtsc
	mov ebx,eax
	mov ecx,edx						;ecx:ebx = starting time

	wbinvd
	mul dword [0x00006FFF]					;*Deliberately split across cache lines*
	wbinvd
	mul dword [0x00005FFF]					;*Deliberately split across cache lines*
	wbinvd
	mul dword [0x00004FFF]					;*Deliberately split across cache lines*
	wbinvd

	rdtsc
	CPU 486
	sti

	sub eax,ebx
	sbb edx,ecx						;edx:eax = cycles taken

	test edx,edx						;Did it take 10 cycles?
	jne .notBochs						; no, not Bochs
	cmp eax,10						;Did it take 10 cycles?
	je .isBochs						; yes, it's Bochs


;Return "Bochs not detected"

.notBochs:
	clc
	ret


;Return VM type is "Bochs"

.isBochs:
	mov al,0x01						;VM type is "Bochs"
	mov edx,bochsString00					;Bochs found string
	stc
	ret

;'RDTSC not supported - use PIT timer.[n]

.BOCHSinvOpcode00:
	sti							;Needed as IRQs disabled when RDTSC tried

;Wait for IRQ0 to occur

	mov eax,[IRQcountTable]					;eax = number of times IRQ0 has occured
.wait1:
	hlt
	cmp [IRQcountTable],eax					;Has IRQ0 occured yet?
	je .wait1						; no, keep waiting


;Find out how many times we can loop while flushing the cache before IRQ0 occurs again

	mov eax,[IRQcountTable]					;eax = number of times IRQ0 has occured
	clr edx
	clr ebp							;ebp:edx = number of times we've looped

.wait2:	wbinvd
	cmp dword [0x00006FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00005FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00004FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00003FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00002FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00001FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00000FFF],0				;*Deliberately split across cache lines*
	add edx,1
	adc ebp,0						;Increase the loop counter
	cmp [IRQcountTable],eax					;Has IRQ0 occured yet?
	je .wait2						; no, keep waiting

;Find out how many times we can loop without flushing the cache before IRQ0 occurs again

	mov eax,[IRQcountTable]					;eax = number of times IRQ0 has occured
	clr ebx
	clr ecx							;ecx:ebx = number of times we've looped

.wait3:	nop
	cmp dword [0x00006FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00005FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00004FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00003FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00002FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00001FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00000FFF],0				;*Deliberately split across cache lines*
	add ebx,1
	adc ecx,0						;Increase the loop counter
	cmp [IRQcountTable],eax					;Has IRQ0 occured yet?
	je .wait3						; no, keep waiting

;Calculate difference (negative values mean it's faster to flush caches than to do a NOP)

	sub ebx,edx
	sbb ecx,ebp						;ecx:ebx = difference
	jc .isBochs						;It's Bochs if the difference is negative
	test ecx,ecx						;Is there a huge difference?
	jne .notBochs						; yes, can't be Bochs (or sane?)
	cmp ebx,10						;Is the difference too small?
	jb .isBochs						; yes, assume it's Bochs
	jmp .notBochs						; no, assume it's not Bochs



;'[t Check For VirtualPC]
;;_______________________________________________________________________________

;'Note: VirtualPC detection was originally written from information at
;'[ahttp://www.codeproject.com/system/VmDetect.asp].[n]

.checkVirtualPC:

;Set new "Invalid Opcode" exception handler

	mov ebx,.VPCinvOpcode
	call setInvalidOpcodeException

;Do the special invalid instruction (should cause an invalid opcode exception)

	db 0x0F,0x3F,0x07,0x0B

;Return VM type is "Virtual PC"

	mov al,0x03						;Set VM type to "Virtual PC"
	mov edx,virtualPCstring00
	stc
	ret

;'Invalid opcode received.[n]

.VPCinvOpcode:
	clc							;Return with "not found"
	ret


;'[t Check For VMware]
;;_______________________________________________________________________________

;'Note: VMware detection was originally written from information at
;'[ahttp://chitchat.at.infoseek.co.jp/vmware/backdoor.html].[n]

.checkVMware:
	mov eax,0x564D5868					;eax = magic number
	clr ebx							;ebx = anything except the magic number
	mov ecx,0x0000000A					;ecx = function number (Get VMWare Version)
	mov edx,0x00005658					;edx = I/O port number
	in eax,dx						;Trigger special function

	cmp ebx,0x564D5868					;Is the magic number in EBX now?
	je .isVMware						; yes, running under VMWare
	clc
	ret

;'VMware Detected - Determine Version.[n]

.isVMware:
	mov al,0x04						;Set VM type to "VMWare"

	cmp ecx,1						;Is it VMware Express?
	jb .VMWunknown						; no, unknown version
	je .VMWexpress						; yes, VMware Express
	cmp ecx,3						;Is it GSX Server?
	jb .VMWESXserver					; no, ESX Server
	je .VMWGSXserver					; yes, GSX Server
	cmp ecx,4						;Is it Workstation?
	je .VMWworkstation					; yes, Workstation

.VMWunknown:
	mov edx,VMwareString00
	stc
	ret

.VMWexpress:
	mov edx,VMwareString01
	stc
	ret

.VMWESXserver:
	mov edx,VMwareString02
	stc
	ret

.VMWGSXserver:
	mov edx,VMwareString03
	stc
	ret

.VMWworkstation:
	mov edx,VMwareString04
	stc
	ret


;'[t Check For Qemu]
;;_______________________________________________________________________________

;'For modern computers (or computers modern enough to support the [iRDTSC]
;'instruction) caches are used to minimize slow memory access times. Qemu
;'doesn't emulate these caches.[n]
;'The following code deliberately causes lots of cache misses in a loop
;'(by flushing the caches), and then compares the time it took to do a similar
;'loop without flushing the cache. On a real computer the first loop is very
;'slow because of the cache misses while the second loop is very fast because
;'everything is already in the cache. For Qemu, both loops usually take roughly
;'the same amount of time.[n]
;'Unfortunately, the timing for each loop can vary considerably on Qemu
;'(possibly due to interrupts on the host), so this test is repeated many
;'times.[n]
;'I tested this on several real computers to confirm it's accuracy (to make
;'sure it doesn't accidentally decide Qemu is running). For all of the
;'computers I tested the first loop is between 1000 times slower and 2200
;'times slower. For Qemu, the results are often similar and the first loop
;'is sometimes faster than the second.[n]
;'This test must be the last test, as it will probably detect other emulators
;'(like Bochs) and we wouldn't know which emulator was found.[n]
;'Note: Qemu can't emulate older CPUs, so if RDTSC isn't supported we're not
;'running on Qemu.[n]

.checkQemu:

;Set new "Invalid Opcode" exception handler

	mov ebx,.QEMUinvOpcode
	call setInvalidOpcodeException

;Check the instruction timings while flushing the cache

	mov esi,100						;esi = number of times to try it

.retry:
	cli
	CPU 586
	rdtsc
	mov ebx,eax
	mov ecx,edx						;ecx:ebx = starting time

	mov edi,200
.q1:	wbinvd
	cmp dword [0x00006FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00005FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00004FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00003FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00002FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00001FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00000FFF],0				;*Deliberately split across cache lines*
	sub edi,1
	jne .q1

	rdtsc
	CPU 486
	sti

	sub eax,ebx
	sbb edx,ecx						;edx:eax = cycles taken
	mov ebx,eax
	mov ecx,edx						;ecx:ebx = cycles taken

;Check the instruction timings

	cli
	CPU 586
	rdtsc
	add ebx,eax
	adc ecx,edx						;ecx:ebx = expected ending time if no cache is present

	mov edi,200
.q2:	nop
	cmp dword [0x00006FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00005FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00004FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00003FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00002FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00001FFF],0				;*Deliberately split across cache lines*
	cmp dword [0x00000FFF],0				;*Deliberately split across cache lines*
	sub edi,1
	jne .q2

	rdtsc
	CPU 486
	sti

	sub ebx,eax
	sbb ecx,edx						;ecx:ebx = how much longer first loop took (in cycles)
	jc .isQEMU						;Definately Qemu if it took less time without cache flushing
	test ecx,ecx						;Was the difference impossibly huge?
	jne .notQemu						; yes, definately not Qemu (or sane?)
	cmp ebx,500						;Was the difference small?
	jb .isQEMU						; yes, it is Qemu
	sub esi,1						;esi = number of attempts remaining
	jne .retry						;Retry until [iesi=0] to make sure

.notQemu:
	clc
	ret

;Return VM type is "Qemu"

.isQEMU:
	mov al,0x02						;Set VM type to "Qemu"
	mov edx,qemuString00
	stc
	ret


;'"Invalid Opcode" Exception Handler.[n]

.QEMUinvOpcode:
	sti							;Needed as IRQs disabled when RDTSC tried
	clc
	ret

