; =============================================================================
;
;                             Litos8 CPU service
;
; =============================================================================

; -----------------------------------------------------------------------------
; Exported user functions (2):
;	GetCPUInfo - get CPU info structure
;	GetCPUInfoText - get CPU info text
; -----------------------------------------------------------------------------

; ------------- Macro - short delay (roughly 1.5 microsecond)
; Alternatives of dummy ports:
;	80h: AT diagnostic port used by Linux for short delay
;	0ebh: dummy port used by some BIOSes for short delays
;	0edh: dummy port used by some BIOSes for short delays

%define		SHORT_DELAY	out 80h,al

; ------------- macro - check if FPU numeric coprocessor is present
; returns NZ = FPU is present

%macro		IS_FPU 0
		test	byte [CPUFlags],CPU_FPU ; check FPU flag
%endmacro

; ------------- macro - check if CPUID instruction is supported
; returns NZ = CPUID instruction is supported

%macro		IS_CPUID 0
		test	byte [CPUFlags],CPU_CPUID ; check CPUID flag
%endmacro

; ------------- macro - check if RDTSC instruction is supported
; returns NZ = RDTSC instruction is supported

%macro		IS_RDTSC 0
		test	byte [CPUFlags],CPU_RDTSC ; check RDTSC flag
%endmacro

; ------------- macro - check if MMX instructions are supported
; returns NZ = MMX instructions are supported

%macro		IS_MMX 0
		test	byte [CPUFlags],CPU_MMX ; check MMX flag
%endmacro

; ------------- macro - check if SSE instructions are supported
; returns NZ = SSE instructions are supported

%macro		IS_SSE 0
		test	byte [CPUFlags],CPU_SSE ; check SSE flag
%endmacro

; ------------- macro - check if SSE2 instructions are supported
; returns NZ = SSE2 instructions are supported

%macro		IS_SSE2 0
		test	byte [CPUFlags],CPU_SSE2 ; check SSE2 flag
%endmacro

		CODEINIT_SECTION

; -----------------------------------------------------------------------------
;                            INIT: Initialize CPU
; -----------------------------------------------------------------------------

; ------------- initialize CPU info structure

InitCPU:
		mov	word [CPUVendorID],CPUVendorIDText ; vendor
		mov	word [CPUEquipment],CPUEquipText ; equipment list

; ------------- detect numeric coprocessor

		test	byte [Equipment],B1 ; numeric coprocessor present?
		jz	short InitCPU1	; no numeric coprocessor
%ifndef NOFPU
		or	byte [CPUFlags],CPU_FPU ; set FPU flag
		finit			; initialize FPU
%endif

; ------------- detect 80186 and more (FLAG bits 12..15 cannot be cleared to 0)

InitCPU1:	pushf			; push flags
		xor	ax,ax		; AX <- 0
		push	ax		; 0 -> stack
		popf			; flags <- 0
		pushf			; flags -> stack
		pop	ax		; AX <- flags
		popf			; pop flags
		and	ah,0f0h		; check bits 12..15
		cmp	ah,0f0h		; can be changed?
%if MAXCPUTYPE < CPU8086
		mov	al,CPUUnknown
		jmp	short InitCPU2
%endif

%if MAXCPUTYPE > CPUV30
		jne	short InitCPU3	; can be changed - 80186 or more
%endif

; ------------- detect V30 (MUL AL instruction does not change ZF)

		xor	al,al		; set ZF
		mov	al,40h		; AL <- 40h, some number
		mul	al		; AX <- multiply 40h * 40h
		mov	al,CPU8086	; 8086
%if MAXCPUTYPE <= CPU8086
		jmp	short InitCPU2
%endif
		jnz	short InitCPU2	; ZF changed - 8086
		mov	al,CPUV30	; ZF not changed - V30
InitCPU2:	jmp	InitCPU8	; set CPU type

; ------------- detect 80186 (PUSH SP saves SP after operation)

InitCPU3:	push	sp		; push SP
		pop	bx		; BX <- SP
		cmp	bx,sp		; pushed state before operation?
		mov	al,CPU186	; CPU 80186
%if MAXCPUTYPE <= CPU186
		jmp	short InitCPU2
%endif
		jne	short InitCPU2	; SP not as before push - 80186

; ------------- detect 80286 (FLAG bits 12..15 cannot be set to 1)

		pushf			; push flags
		mov	ax,0f000h	; AX <- F000h, flags 12..15 set to 1
		push	ax		; F000h -> stack
		popf			; flags <- F000h
		pushf			; flags -> stack
		pop	ax		; AX <- flags
		popf			; pop flags
		and	ah,0f0h		; check bits 12..15
		mov	al,CPU286	; AL <- CPU 80286
%if MAXCPUTYPE <= CPU286
		jmp	short InitCPU2
%endif
		jz	short InitCPU2	; bits 12..15 cannot be set - 80286

; ------------- detect 80386 (cannot change AC flag bit, align error interrupt)

		CPU	386

		mov	edx,esp		; EDX <- push ESP
		and	esp,~3		; align ESP to DWORD
		pushfd			; push eflags
		cli			; disable interrupts
		pushfd			; eflags -> stack
		pop	eax		; EAX <- eflags
		mov	ecx,eax		; ECX <- store eflags
		xor	eax,B18+B21	; change bits AC and ID
		push	eax		; push EAX
		popfd			; set eflags
		pushfd			; push eflags
		pop	eax		; EAX <- eflags
		popfd			; pop eflags
		mov	esp,edx		; ESP <- pop ESP
		xor	eax,ecx		; EAX <- changed bits
		shr	eax,8		; AH <- B18 as B2, B21 as B5

		CPU	8086

		mov	al,CPU386	; AL <- CPU 80386
		test	ah,B2		; bit AC (B18) changed?
%if MAXCPUTYPE <= CPU386
		jmp	short InitCPU2
%endif
		jz	short InitCPU2	; bit AC cannot be changed - 80386

; ------------- detect 80486 (cannot change ID flag bit, CPUID not supported)

		test	ah,B5		; bit ID (B21) changed?
%if MAXCPUTYPE > CPU486
		jnz	short InitCPU4	; bit ID can be changed - Pentium
%endif
		jmp	InitCPU7	; bit ID cannot be changed - 80486

; ------------- load Pentium info

InitCPU4:
%ifdef NOCPUID
		jmp	short InitCPU69
%endif
		CPU	586

		xor	eax,eax		; EAX <- 0
		xor	ebx,ebx		; EBX <- 0
		cpuid			; CPUID
		or	ebx,ebx		; vendor 1 valid?
		jz	short InitCPU7	; vendor 1 invalid, use 80486
		mov	[CPUVendorIDText+2],ebx ; vendor 1
		mov	[CPUVendorIDText+6],edx ; vendor 2
		mov	[CPUVendorIDText+10],ecx ; vendor 3
		mov	byte [CPUVendorIDText],12 ; length of vendor text

		CPU	8086

		or	byte [CPUFlags],CPU_CPUID ; set CPUID flag

; ------------- find vendor

		mov	bx,CPUVendor	; BX <- vendor code
		mov	byte [bx],CPU_VEND_NUM-1 ; preset to last code
		mov	di,CPUVendString2 ; DI <- last string
InitCPU5:	mov	cx,6		; CX <- text length
		mov	si,CPUVendorIDText+2 ; SI <- compared vendor string
		push	di		; push DI
		repe	cmpsw		; compare one string
		pop	di		; pop DI
		je	short InitCPU6	; vendor has been found
		sub	di,byte 12	; DI <- previous string
		dec	byte [bx]	; decrease vendor code
		jnz	short InitCPU5	; test next vendor string

; ------------- get CPU information (here EAX = max. CPUID function) -> DX:AX

InitCPU6:	or	ax,ax		; function 1 supported?
		jz	short InitCPU69	; function 1 not supported

		CPU	586

		mov	eax,1		; EAX <- required function code
		cpuid			; CPUID with function 1
		mov	eax,edx		; EAX <- equipment
		shr	edx,16		; DX <- equipment HIGH

		CPU	8086

; ------------- set equipment flags

		test	al,B4		; check TSC time stamp counter
		jz	short InitCPU62	; no TSC time stamp counter
%ifndef NORDTSC
		or	byte [CPUFlags],CPU_RDTSC ; set RDTSC flag
%endif

InitCPU62:	test	dl,B7		; B23: check MMX instruction set
		jz	short InitCPU63	; no MMX instruction set
%ifndef NOMMX
		or	byte [CPUFlags],CPU_MMX ; set MMX flag
%endif

InitCPU63:	test	dh,B1		; B25: check SSE instruction set
		jz	short InitCPU64	; no SSE instruction set
%ifndef NOSSE
		or	byte [CPUFlags],CPU_SSE ; set SSE flag
%endif

InitCPU64:	test	dh,B2		; B26: check SSE2 instruction set
		jz	short InitCPU69	; no SSE2 instruction set
%ifndef NOSSE2
		or	byte [CPUFlags],CPU_SSE2 ; set SSE2 flag
%endif

InitCPU69:	mov	al,CPUPent	; AL <- CPU Pentium
		test	byte [CPUFlags],CPU_RDTSC ; support RDTSC ?
		jnz	short InitCPU8	; yes, otherwise it must be some 486

; ------------- set CPU type

InitCPU7:	mov	al,CPU486	; AL <- CPU 80486
InitCPU8:	mov	[CPUType],al	; set CPU type

; ------------- prepare CPU name

		mov	bh,0		; BH <- 0
		mov	bl,[CPUType]	; BX <- CPU type
		shl	bx,1		; BX <- offset in address table
		mov	ax,[CPUNameList+bx] ; AX <- CPU name
		mov	[CPUName],ax	; set CPU name

; ------------- prepare vendor name

		mov	bh,0		; BH <- 0
		mov	bl,[CPUVendor]	; BX <- CPU vendor
		mov	ax,CPUVendorIDText ; AX <- unknown vendor
		shl	bx,1		; BX <- offset in address table
		jz	short InitCPU9	; unknown vendor
		mov	ax,[CPUVendorList+bx] ; AX <- CPU vendor name
InitCPU9:	mov	[CPUVendorName],ax ; set CPU vendor name

; ------------- generate equipment list

		mov	bx,CPUEquipList	; BX <- equipment list
		mov	di,CPUEquipText+2 ; DI <- destination buffer
		mov	ax,B0*256 + " "	; AH <- bit mask, AL <- space char

InitCPU92:	test	[CPUFlags],ah	; check flag
		jz	short InitCPU94	; flag not set

		mov	si,[bx]		; SI <- equipment name
		mov	cx,[si]		; CX <- text length
		inc	si
		inc	si		; SI <- start of text
		rep	movsb		; copy equipment text
		stosb			; store space character

InitCPU94:	inc	bx		; BX += 1
		inc	bx		; BX <- shift equipment pointer
		shl	ah,1		; AH <- next bit flag
		jnc	short InitCPU92	; next flag

		sub	di,CPUEquipText+2 ; DI <- text length
		jz	short InitCPU95	; no text
		dec	di		; DI <- without end space
InitCPU95:	mov	[CPUEquipText],di ; set text length
		ret

; -----------------------------------------------------------------------------
;                         INIT: Detect CPU frequency
; -----------------------------------------------------------------------------
; NOTES:	It uses TIMER 2 counter.
; -----------------------------------------------------------------------------

; ------------- disable interrupts

InitCPUFreq:	pushf			; push flags
		cli			; disable interrupts

; ------------- disable output to PC speaker and enable Timer 2 output

		mov	al,0		; AL <- disable flag
		call	SetSpeakerGate	; disable speaker output
		inc	ax		; AL <- 1, enable flag
		call	SetTimer2Gate	; enable Timer 2 output

; ------------- set Timer 2 counter to 65536 (period 54.92542 ms)

		mov	al,TIMER_MODE_CNT ; counter mode
		mov	dl,2		; DL <- counter 2
		xor	cx,cx		; CX <- initial value
		call	TimerSetMode	; start Timer 2 in mode 0

; ------------- check if time-stamp counter is supported

		IS_RDTSC		; is time-stamp counter supported?
		jz	short InitCPUFreq5; time-stamp counter is not supported

; ------------- read time-stamp counter, start time -> EDI:ESI

		CPU	586

		rdtsc			; read time-stamp counter (-> EDX:EAX)
		xchg	eax,esi		; ESI <- start time LOW
		mov	edi,edx		; EDI <- start time HIGH

; ------------- wait for Timer 2 output

InitCPUFreq2:	call	GetTimer2Out	; get Timer 2 output
		jc	short InitCPUFreq2 ; wait for end of Timer 2 counting

; ------------- read time-stamp counter, get interval -> EDX:EAX

		rdtsc			; read time-stamp counter (-> EDX:EAX)
		sub	eax,esi		; EAX <- interval LOW
		sbb	edx,edi		; EDX <- interval HIGH

; ------------- limit counter value -> EDX:EAX

		cmp	edx,byte 35h	; maximal value
		jb	short InitCPUFreq4 ; frequency is OK
		mov	edx,35h		; EDX <- limit maximal value HIGH
		or	eax,-1		; EAX <- limit maximal value LOW

; ------------- calculate frequency -> DX:AX
		
InitCPUFreq4:	shld	edx,eax,25	; shift EDX:EAX << 25 (e.g. * 2^25)
		shl	eax,25		; shift EAX << 25
		mov	ecx,1842991270	; timer divisor (=54.92542*(2^25))
		div	ecx		; calculate frequency
		mov	edx,eax		; EDX <- frequency
		shr	edx,16		; DX <- frequency HIGH

		CPU 	8086

		jmp	InitCPUFreq8

; ------------- prepare registers

InitCPUFreq5:	mov	di,8000h	; DI <- multiplier
		xor	ax,ax		; AX <- 0
		mov	cx,1000		; CX <- number of loops

; ------------- waste CPU time

InitCPUFreq6:	
		%rep	10
		mul	di		; one CPU delay
		%endrep
		loop	InitCPUFreq6	; next loop

; ------------- read counter (-> CX)

		mov	dl,2		; DL <- counter 2
		call	TimerGetVal	; get timer 2 value -> AX
		xchg	ax,cx		; CX <- timer 2 value
		neg	cx		; CX <- counter

%ifdef DEB_CPUSPEEDCOEF
		mov	[CPUSpeedCoef],cx

; Calculate coeficient: coef = freq(kHZ) * cx

; Pentium 4 @ 2.594 GHz: 73 -> 189362000 (B496F50)
; Pentium 3 @ 501.1 MHz: 120 -> 60132000 (3958AA0)
; Pentium Pro-S @ 199.8 MHz: 303 -> 60600000 (39CAEC0)
; Celeron 666 (Pentium Intel 668.202 MHz): 90 -> 60138180 (395A2C4)

; AMD K6/300 (Pentium AMD 300.688 MHz): 119 -> 35781872 (221FCF0)

; Compaq XE 486DX @ 66 MHz: 4781 -> 315546000 (12CED990)

; Amd386DX/25 MHz: 12512 -> 312800000 (12A4F300)
; Amd386DX/40 MHz: 7812 -> 312480000 (12A01100)

; aprox: 486 : 286 = 310374 : 25254
; 286 -> 25000000 (17D7840)
; aprox: 486 : 86 = 310374 : 10000
; 86 -> 10080000 (99CF00)

%endif

; ------------- calculate frequency

		jcxz	InitCPUFreq82	; invalid counter
		xor	bx,bx		; BX <- coefficient LOW

		; Pentium (but Pentium uses RDTSC! ... so only for debug)
		;mov	ax,398h		; AX <- coefficient HIGH
		;cmp	byte [CPUType],CPUPent
		;jae	short InitCPUFreq72

		; 486
		mov	ax,12D0h	; AX <- coefficient HIGH
		cmp	byte [CPUType],CPU486
		jae	short InitCPUFreq72

		; 386
		mov	ax,12A0h	; AX <- coefficient HIGH
		cmp	byte [CPUType],CPU386
		jae	short InitCPUFreq72

		; 286
		mov	ax,180h		; AX <- coefficient HIGH
		cmp	byte [CPUType],CPU286
		jae	short InitCPUFreq72

		; 186 or less
		mov	ax,9ah		; AX <- coefficient HIGH

InitCPUFreq72:	xor	dx,dx		; DX <- 0
		div	cx		; divide coefficient HIGH
		xchg	ax,bx		; AX <- coefficient LOW, BX <- result HIGH
		div	cx		; divide coefficient LOW
		mov	dx,bx		; DX <- result HIGH

; ------------- limit minimal value

InitCPUFreq8:	or	dx,dx		; check minimal value HIGH
		jnz	short InitCPUFreq9 ; frequency is OK
		cmp	ax,2000		; check minimal value LOW
		ja	short InitCPUFreq9 ; frequency is OK
InitCPUFreq82:	xor	dx,dx		; DX <- limit minimal value HIGH
		mov	ax,2000		; AX <- limit minimal value LOW
InitCPUFreq9:	mov	[CPUFreq],ax	; set CPU frequency LOW
		mov	[CPUFreq+2],dx	; set CPU frequency HIGH

; ------------- disable Timer 2 output

		mov	al,0		; AL <- disable flag
		call	SetTimer2Gate	; disable Timer 2 output

; ------------- set Timer 2 default speed (tone generator, aprox. 900 Hz)

		mov	ch,5		; CX <- aprox. 900 Hz
		mov	dl,2		; DL <- counter 2
		call	TimerSetVal	; set Timer 2 speed (speaker)

; ------------- re-enable interrupts

		popf			; pop flags
		ret


		CODE_SECTION

; -----------------------------------------------------------------------------
;                         Get CPU info structure
; -----------------------------------------------------------------------------
; OUTPUT:	BX = pointer to CPU info structure CPUINFO
; -----------------------------------------------------------------------------

GetCPUInfo:	mov	bx,CPUInfo
		ret

; -----------------------------------------------------------------------------
;                           Get CPU info TEXT
; -----------------------------------------------------------------------------
; OUTPUT:	BX = pointer to TEXT structure (NULL on memory error)
;		CY = memory error (BX = NULL)
; NOTES:	Returned text should be destroyed using TextFree.
; -----------------------------------------------------------------------------

; ------------- push registers

GetCPUInfoText:	push	ax		; push AX
		push	dx		; push DX
		push	si		; push SI

; ------------- CPU name

		mov	si,CPUInfo	; SI <- CPU info structure CPUINFO
		mov	bx,[si+CPUINFO_Name] ; BX <- CPU name
		call	TextDup		; text duplicate
		xchg	ax,bx		; BX <- output text or NULL
		jc	short GetCPUInfoText9 ; memory error
		call	TextAddSpc	; add space character

;-------------- vendor name

		mov	ax,[si+CPUINFO_VendNam] ; AX <- pointer to CTEXT vendor
		call	TextAddText	; add text
		call	TextAddSpc	; add space character

; ------------- CPU frequency

		mov	ax,[si+CPUINFO_Freq]
		mov	dx,[si+CPUINFO_Freq+2] ; DX:AX <- frequency in kHz
		call	TextAddDWord	; add frequency
		mov	ax,CPUTextKHz	; AX <- text " kHz, "
		call	TextAddText	; add text " kHz, "

; ------------- CPU equipment

		mov	ax,[si+CPUINFO_Equip]
		call	TextAddText	; add equipment text

; ------------- pop registers

		clc			; clear error flag
GetCPUInfoText9:pop	si		; pop SI
		pop	dx		; pop DX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                            Constant data
; -----------------------------------------------------------------------------

		CONST_SECTION

; ------------- CPU type name

CPUNameUnknown:	CTEXT	"x86"
CPUName8086:	CTEXT	"8086"
CPUNameV30:	CTEXT	"NEC V30"
CPUName186:	CTEXT	"80186"
CPUName286:	CTEXT	"80286"
CPUName386:	CTEXT	"80386"
CPUName486:	CTEXT	"80486"
CPUNamePent:	CTEXT	"Pentium"

CPUNameList:	dw	CPUNameUnknown
		dw	CPUName8086
		dw	CPUNameV30
		dw	CPUName186
		dw	CPUName286
		dw	CPUName386
		dw	CPUName486
		dw	CPUNamePent

; ------------- vendor strings

CPUVendString:	db	'GenuineIntel'	; Intel
		db	'AuthenticAMD'	; AMD
		db	'CyrixInstead'	; Cyrix
		db	'CentaurHauls'	; Centaur
		db	'NexGenDriven'	; NexGen
		db	'GenuineTMx86'	; Transmeta
		db	'RiseRiseRise'	; Rise
		db	'UMC UMC UMC '	; UMC
		db	'SiS SiS SiS '	; SiS
		db	'VIA VIA VIA '	; VIA
		db	'Vortex86 SoC'	; Vortex
CPUVendString2:	db	'Geode by NSC'	; National Semiconductor

; ------------- vendor name

CPUVendNo:	CTEXT	""
CPUVendIntel:	CTEXT	"Intel"
CPUVendAMD:	CTEXT	"AMD"
CPUVendCyrix:	CTEXT	"Cyrix"
CPUVendCent:	CTEXT	"Centaur"
CPUVendNexGen:	CTEXT	"NexGen"
CPUVendTrans:	CTEXT	"Transmeta"
CPUVendRise:	CTEXT	"Rise"
CPUVendUMC:	CTEXT	"UMC"
CPUVendSiS:	CTEXT	"SiS"
CPUVendVIA:	CTEXT	"VIA"
CPUVendVortex:	CTEXT	"Vortex"
CPUVendNSC:	CTEXT	"National Semiconductor"

CPUVendorList:	dw	CPUVendNo
		dw	CPUVendIntel
		dw	CPUVendAMD
		dw	CPUVendCyrix
		dw	CPUVendCent
		dw	CPUVendNexGen
		dw	CPUVendTrans
		dw	CPUVendRise
		dw	CPUVendUMC
		dw	CPUVendSiS
		dw	CPUVendVIA
		dw	CPUVendVortex
		dw	CPUVendNSC

; ------------- equipment names

CPUEquipFPU:	CTEXT	"FPU"		; 3+1 chars
CPUEquipCPUID:	CTEXT	"CPUID"		; 5+1 chars
CPUEquipRDTSC:	CTEXT	"RDTSC"		; 5+1 chars
CPUEquipMMX:	CTEXT	"MMX"		; 3+1 chars
CPUEquipSSE:	CTEXT	"SSE"		; 3+1 chars
CPUEquipSSE2:	CTEXT	"SSE2"		; 4+1 chars

CPUEquipList:	dw	CPUEquipFPU
		dw	CPUEquipCPUID
		dw	CPUEquipRDTSC
		dw	CPUEquipMMX
		dw	CPUEquipSSE
		dw	CPUEquipSSE2

CPUEQMAXLEN	EQU	4+6+6+4+4+5	; equipment list max length

CPUTextKHz:	CTEXT	" kHz, "

; -----------------------------------------------------------------------------
;                            Uninitialised data
; -----------------------------------------------------------------------------

		DATA_SECTION

; ------------- CPU info structure CPUINFO

		align	4, resb 1
CPUInfo:
CPUType:	resb	1		; 0: CPU type
CPUVendor:	resb	1		; 1: CPU vendor
CPUFlags:	resw	1		; 2: CPU flags
CPUFreq:	resd	1		; 4: CPU frequency in kHz
CPUName:	resw	1		; 8: pointer to CTEXT CPU name
CPUVendorID:	resw	1		; 10: pointer to CTEXT vendor ident.
CPUVendorName:	resw	1		; 12: pointer to CTEXT vendor name
CPUEquipment:	resw	1		; 14: point. to CTEXT, equipment list

		align	2, resb 1
CPUVendorIDText:resb	2+12		; CTEXT vendor ident.
		align	2, resb 1
CPUEquipText:	resb	2+CPUEQMAXLEN	; CTEXT equipment list

%ifdef DEB_CPUSPEEDCOEF
CPUSpeedCoef:	resw	1
%endif
