								; pm9a.asm
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	pm9a.asm - protected-mode demo code
;	Christopher Giese <geezer[AT]execpc.com>
;
;	Release date 8/27/98. Distribute freely. ABSOLUTELY NO WARRANTY.
;	Assemble with NASM:	nasm -o pm9a.com pm9a.asm
;
; Demonstrates:
;	- Transitions between Ring 0 (system/kernel mode) and Ring 3
;	  (user mode).
; Notes:
;	- Code to return to real mode removed for clarity.
;	  Put it back in if you like.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[SECTION .text]
[ORG 0x100]
[BITS 16]
; set base of code/data descriptors to CS<<4/DS<<4 (CS=DS)
	xor ebx,ebx
	mov bx,cs		; EBX=segment
	shl ebx,4		;	<< 4
	lea eax,[ebx]		; EAX=linear address of segment base
	mov [gdt2 + 2],ax
	mov [gdt3 + 2],ax
	mov [gdt4 + 2],ax
	mov [gdt5 + 2],ax
	shr eax,16
	mov [gdt2 + 4],al
	mov [gdt3 + 4],al
	mov [gdt4 + 4],al
	mov [gdt5 + 4],al
	mov [gdt2 + 7],ah
	mov [gdt3 + 7],ah
	mov [gdt4 + 7],ah
	mov [gdt5 + 7],ah
; fix up TSS entry, too. x86 task-switch mechanism is not used, but the
; TSS is needed to specify the location of system (Ring 0) and user
; (Ring 3) stack pointers.
	lea eax,[ebx + tss]	; EAX=linear address of tss
	mov [gdt6 + 2],ax
	shr eax,16
	mov [gdt6 + 4],al
	mov [gdt6 + 7],ah
; point gdtr to the gdt, idtr to the idt
	lea eax,[ebx + gdt]	; EAX=linear address of gdt
	mov [gdtr + 2],eax
	lea eax,[ebx + idt]	; EAX=linear address of idt
	mov [idtr + 2],eax
; clear NT bit (so iret does normal iret, instead of task-switch),
; set IOPL=00, and set IF=0 (disable interrupts)
	push dword 0
	popfd
; load GDT and IDT for full protected mode
	lgdt [gdtr]
	lidt [idtr]
; set PE [protected mode enable] bit and go
	mov eax,cr0
	or al,1
	mov cr0,eax
	jmp SYS_CODE_SEL:do_pm
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	32-bit protected mode, ring 0 (kernel/system mode)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[BITS 32]
do_pm:	mov ax,SYS_DATA_SEL
	mov ds,ax
	mov ss,ax
	nop
	mov es,ax
	mov fs,ax
	mov gs,ax
; load task register.
	mov ax,USER_TSS
	ltr ax
; print starting msg
	lea esi,[msg1]
	call wrstr
; set Ring 0 and Ring 3 stack pointers in the TSS
	mov [tss_esp0],esp	; ring 0 task uses system stack
	lea eax,[esp - 256]
	mov [tss_esp],eax	; ring 3 task stack is 256 bytes lower
; move_to_user_mode, from Linux 0.01 (linux/include/asm/system.h):
	mov eax,esp
	push dword USER_DATA_SEL ; SS
	push eax		; ESP
	push dword 0x00		; EFLAGS
	push dword USER_CODE_SEL ; CS
	lea eax,[ring3]
	push eax		; EIP
	iret			; jumps to ring3
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	32-bit protected mode, ring 3 (user mode)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ring3:	mov ax,USER_DATA_SEL
	mov ds,ax
	mov es,ax
	mov fs,ax
	mov gs,ax
; print CS register to prove we're in Ring 3
	xor eax,eax
	mov ax,cs
	call hexout
	lea esi,[msg2]
	call wrstr
; demonstrate interrupt 0x20 (syscall)
	int 0x20
; another message
	xor eax,eax
	mov ax,cs
	call hexout
	lea esi,[msg4]
	call wrstr
; DPL of all IDT entries except int 0x20 is Ring 0, so int 0x18 causes
; GPF instead of int 0x18. It doesn't matter -- all interrupts but
; syscall point to ring0, below.
	int 0x18
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	32-bit protected mode, ring 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ring0:	mov ax,SYS_DATA_SEL
	mov ds,ax
	mov es,ax
	mov fs,ax
	mov gs,ax
; print CS register to prove it
	xor eax,eax
	mov ax,cs
	call hexout
	lea esi,[msg5]
	call wrstr
; freeze
	jmp $
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	"You are the Syscall. You are of Kernel."
;
;	(My apologies to those who don't watch DS9 :)
;
;	The CPU pushes user-mode SS, ESP, EFLAGS, CS, and EIP;
;	then sets SS:ESP and CS:EIP. We have to do the rest.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
syscall:push gs			; save other user-mode seg regs
	push fs
	push es
	push ds
	pusha			; save user-mode GP regs
		mov ax,ss	; SS was set to SYS_DATA_SEL by interrupt
		mov ds,ax
		mov es,ax
		mov fs,ax
; prove that the ring transition worked
		xor eax,eax
		mov ax,cs
		call hexout
		lea esi,[msg3]
		call wrstr
	popa
	pop ds
	pop es
	pop fs
	pop gs
	iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	QuickHack(TM) character-output video routine
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
wrch:	push gs
	push ebx
		mov bx,LINEAR_SEL	; RPL=3 and DPL=3, so both Ring 0
		mov gs,bx		; and Ring 3 code can use it.
		xor ebx,ebx
		mov bx,[CsrPos]
		add ebx,0xB8000
		mov [gs:ebx],al
		inc word [CsrPos]
		inc word [CsrPos]
	pop ebx
	pop gs
	ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	string-output video routine
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
wrstr:	push esi
	push eax
		cld
		jmp wrstr2
wrstr1:		call wrch
wrstr2:		lodsb
		or al,al
		jne wrstr1
	pop eax
	pop esi
	ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	an even uglier hex value output routine...
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
hexout:	push eax
		shr eax,16
		call hexo2
	pop eax
hexo2:	push eax
		mov al,ah
		call hexo3
	pop eax
hexo3:	push eax
		shr al,4
		call hexo4
	pop eax
hexo4:	and al,0x0F
	add al,'0'
	cmp al,'9'
	jbe hexo5
	add al,7
hexo5:	call wrch
	ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	data
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CsrPos:	dw 0

msg1:	db "Greetings from 32-bit Ring 0 (system/ker"
	db "nel mode).                              CS=0x", 0

msg2:	db " - lower 2 bits of CS indic"
	db "ate we're in Ring 3 (user mode).        CS=0x", 0

msg3:	db " - syscall switched us back"
	db " to Ring 0.                             CS=0x", 0

msg4:	db " - back to user mode. Help,"
	db " I'm getting dizzy...                   CS=0x", 0

msg5:	db " - back to Ring 0. System halted.", 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	16-bit limit/32-bit linear base address of GDT and IDT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
gdtr:	dw gdt_end - gdt - 1	; GDT limit
	dd gdt			; linear, physical address of GDT

idtr:	dw idt_end - idt - 1	; IDT limit
	dd idt			; linear, physical address of IDT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	global descriptor table (GDT)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; null descriptor
gdt:	dw 0			; limit 15:0
	dw 0			; base 15:0
	db 0			; base 23:16
	db 0			; type
	db 0			; limit 19:16, flags
	db 0			; base 31:24
; linear data segment descriptor
LINEAR_SEL	equ	$-gdt+3	; RPL=3
	dw 0xFFFF		; limit 0xFFFFF
	dw 0			; base for this one is always 0
	db 0
	db 0xF2			; present, DPL=ring 3, data, expand-up, writable
	db 0xFC			; page-granular, 32-bit
	db 0
; code segment descriptor
SYS_CODE_SEL	equ	$-gdt
gdt2:	dw 0xFFFF
	dw 0			; (base gets set above)
	db 0
	db 0x9A			; present, ring 0, code, non-conforming, readable
	db 0xFC
	db 0
; data segment descriptor
SYS_DATA_SEL	equ	$-gdt
gdt3:	dw 0xFFFF
	dw 0			; (base gets set above)
	db 0
	db 0x92			; present, ring 0, data, expand-up, writable
	db 0xFC
	db 0
; code segment descriptor
USER_CODE_SEL	equ	$-gdt+3
gdt4:	dw 0xFFFF
	dw 0			; (base gets set above)
	db 0
	db 0xFA			; present, ring 3, code, non-conforming, readable
	db 0xFC
	db 0
; data segment descriptor
USER_DATA_SEL	equ	$-gdt+3
gdt5:	dw 0xFFFF
	dw 0			; (base gets set above)
	db 0
	db 0xF2			; present, ring 3, data, expand-up, writable
	db 0xFC
	db 0
; user TSS
USER_TSS	equ	$-gdt
gdt6:	dw 103
	dw 0			; set to tss
	db 0
	db 0xE9			; present, ring 3, 32-bit available TSS
	db 0
	db 0
gdt_end:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	interrupt descriptor table (IDT)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 32 reserved interrupts:
idt:	dw ring0		; entry point 15:0
	dw SYS_CODE_SEL		; selector
	db 0			; word count
	db 0x8E			; type (32-bit Ring 0 interrupt gate)
	dw 0			; entry point 31:16 (XXX - ring0 >> 16)

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0

	dw ring0
	dw SYS_CODE_SEL
	db 0
	db 0x8E
	dw 0
; a DPL=3 interrupt gate, used for syscalls
	dw syscall
	dw SYS_CODE_SEL
	db 0
	db 0xEE	; DPL=3
	dw 0
idt_end:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	task state segment
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
tss:	dw 0, 0			; back link
tss_esp0:
	dd 0			; ESP0
	dw SYS_DATA_SEL, 0	; SS0, reserved
	dd 0			; ESP1
	dw 0, 0			; SS1, reserved
	dd 0			; ESP2
	dw 0, 0			; SS2, reserved
	dd 0			; CR3
	dd 0, 0			; EIP, EFLAGS (EFLAGS=0x200 for ints)
	dd 0, 0, 0, 0		; EAX, ECX, EDX, EBX
tss_esp:
	dd 0, 0, 0, 0		; ESP, EBP, ESI, EDI
	dw 0, 0			; ES, reserved
	dw 0, 0			; CS, reserved
	dw USER_DATA_SEL, 0	; SS, reserved
	dw 0, 0			; DS, reserved
	dw 0, 0			; FS, reserved
	dw 0, 0			; GS, reserved
	dw 0, 0			; LDT, reserved
	dw 0, 0			; debug, IO perm. bitmap
end:
