;//---------------------------------------------------------------
;//	showtss2.s	     (A solution to Question V on Exam I)
;//
;//	This is a modification of the 'twotasks.s' demo discussed
;//	in class, and it was studied during an in-class exercise.
;//
;//	(The program demonstrated task-switching by the processor
;//	between two tasks; both tasks ran using privilege-level 0
;//	and one of the two tasks used a Local Descriptor Table.)
;//
;//	   assemble with:  $ as86 showtss.s -b showtss.b 
;//	   install using:  $ dd if=showtss.b of=/dev/fd0 seek=1 
;//
;//	NOTE: This code begins executing with CS:IP = 1000:0002. 
;//
;//	programmer: ALLAN CRUSE
;//	date begun: 17 FEB 2004 
;//	completion: 19 FEB 2004 -- added use of an LDT by task #2
;//	revised on: 29 FEB 2004 -- added a display of tasks' TSSs
;//---------------------------------------------------------------

	.WORD	0xABCD			; programming signature
;-----------------------------------------------------------------
main:	mov	ax, cs			; address this segment
	mov	ds, ax			;   with DS register
	pop	dword return_address	; store return-address	
	mov	ss, ax			;  adjust SS register
	mov	esp, #stack1		; and set new stacktop

	call	prepare_for_the_demo	
	call	enter_protected_mode
	call	exec_taskswitch_demo
	call	show_task_state_info	;<--- NEW PROCEDURE ADDED
	call	leave_protected_mode

	push	dword return_address	; recover our exit-address
	retf				; exit to program launcher
;-----------------------------------------------------------------
; EQUATES for our various segment-descriptor selectors
sel_ss	EQU	0x0008			; data-segment selector  
sel_cs	EQU	0x0010			; code-segment selector
sel_es	EQU	0x0004			; vram-segment selector
sel_t1	EQU	0x0018			; task-segment selector
sel_t2	EQU	0x0020			; task-segment selector
sel_ls	EQU	0x0028			;  LDT-segment selector
sel_vs	EQU	0x0030			; vram-segment selector  <-- added
;-----------------------------------------------------------------
	.ALIGN	8
theGDT:	.WORD	0x0000, 0x0000, 0x0000, 0x0000	; null descriptor
	.WORD	0xFFFF, 0x0000, 0x9201, 0x0000	; data descriptor
	.WORD	0xFFFF, 0x0000, 0x9A01, 0x0000	; code descriptor
	.WORD	0x0067, myTSS1, 0x8901, 0x0000	; task descriptor
	.WORD	0x0067, myTSS2, 0x8901, 0x0000	; task descriptor
	.WORD	0x0007, theLDT, 0x8201, 0x0000	;  LDT descriptor
theLDT:	.WORD	0x7FFF, 0x8000, 0x920B, 0x0000	; vram descriptor
;-----------------------------------------------------------------
;-----------------------------------------------------------------
return_address:	.LONG	0		; to store exit-address
myTSS1:	.SPACE	0x68			; task-state segment #1
myTSS2:	.SPACE	0x68			; task-state segment #2
;=================================================================
;======  THE FOLLOWING PROCEDURES ARE EXECUTED BY TASK #1  =======
;=================================================================
;-----------------------------------------------------------------
prepare_for_the_demo:

	; initialize the Task-State Segment for Task #2

	lea	di, myTSS2
	mov	dword 0x20[di], #draw		; initial-EIP
	mov	dword 0x24[di], #0x0000		; initial-EFLAGS
	mov	dword 0x38[di], #stack2		; initial-ESP
	mov	dword 0x48[di], #sel_es		; initial-ES
	mov	dword 0x4C[di], #sel_cs		; initial-CS
	mov	dword 0x50[di], #sel_ss		; initial-SS
	mov	dword 0x54[di], #sel_ss		; initial-DS
	mov	dword 0x60[di], #sel_ls		; static-LDTR
	ret	
;-----------------------------------------------------------------
enter_protected_mode:

	; initialize the Global Descriptor Table Register

	push	#0x0001			; GDT base-address hiword
	push	#theGDT			; GDT base-address loword
	push	#0x0037			; GDT's segment-limit	; <-- changed 
	lgdt	[esp]			; memory-image into GDTR
	add	sp, #6			; discard the three words

	; we disable interrupts while executing in protected-node

	cli				; no device interrupts

	; turn on PE-bit in Control Register 0
 
	mov	eax, cr0		; get machine status
	bts	eax, #0			; set PE-bit to 1
	mov	cr0, eax		; enable protection

	; reload CS, SS, DS with protected-mode selectors

	jmpf	#pm, #sel_cs		; reload register CS
pm:	mov	ax, #sel_ss		
	mov	ss, ax			; reload register SS
	mov	ds, ax			; reload register DS

	; also purge any invalid values left in ES, FS, GS

	xor	ax, ax			; nullify selector
	mov	es, ax			;  in ES register 
	mov	fs, ax			;  in FS register 
	mov	gs, ax			;  in GS register 
	ret
;-----------------------------------------------------------------
;-----------------------------------------------------------------
exec_taskswitch_demo:

	; establish Task #1 as the current task

	mov	ax, #sel_t1	; load TSS1-selector
	ltr	ax		;  into TR register

	; perform an intertask call to Task #2

	callf	#0, #sel_t2	; call to task #2

	ret			; return to 'main'
;-----------------------------------------------------------------
leave_protected_mode:

	; insure segment-attributes are valid for real-mode

	mov	ax, ss			; address 64KB r/w data
	mov	ds, ax			;   using DS register
	mov	es, ax			;    and ES register

	; turn off PE-bit in Control Register 0

	mov	eax, cr0		; get machine status
	btr	eax, #0			; reset PE-bit to 0
	mov	cr0, eax		; reenter real-mode

	; load real-mode segment-addresses into CS, SS, DS, ES

	jmpf	#rm, #0x1000		; reload CS
rm:
	mov	ax, cs			; address this segment
	mov	ss, ax			;   with SS register
	mov	ds, ax			;   also DS register
	mov	es, ax			;   also ES register

	; now we can once again handle device-interrupts

	sti				; interrupts allowed	
	ret				; back to main routine
;-----------------------------------------------------------------
;=================================================================
;===========  STACK AREA ALLOCATED FOR USE BY TASK #1  ===========
;=================================================================
	.align	16			; use paragraph alignment
	.space	512			; storage for task1 stack
stack1:					; labels the top-of-stack
;=================================================================
;===========  STACK AREA ALLOCATED FOR USE BY TASK #2  ===========
;=================================================================
	.align	16			; use paragraph alignment
	.space	512			; storage for task2 stack
stack2:					; labels the top-of-stack
;=================================================================
;-----------------------------------------------------------------
;=================================================================
;=======  THE FOLLOWING PROCEDURE IS EXECUTED BY TASK #2  ========
;=================================================================
;-----------------------------------------------------------------
msg:	.ASCII	" Hello from task #2 "	; message string
len:	.WORD	* - msg			; message length
att:	.BYTE	0x1F			; message colors
;-----------------------------------------------------------------
draw:
;
; This procedure will draw a message directly into video memory.
; It expects DS already holds a selector for this segment (where
; the message-string is defined) and ES already holds a selector
; for the video memory segment where the string will be written.
;
	mov	si, #msg		; point DS:SI to string
	mov	di, #320		; point ES:DI to screen
	cld				; do forward processimg
	mov	ah, att			; color-attributes in AH
	mov	cx, len			; string's length in CX
nxchr:	lodsb				; fetch next character
	stosw				; store char and color
	loop	nxchr			; process entire string

	; here executing 'iret' will now trigger a task-switch 

	iret				; back to calling task
;-----------------------------------------------------------------
;=================================================================
;===  BELOW IS ADDITIONAL CODE FOR A NEW PROCEDURE IN TASK #1  ===
;================================================================= 
;-----------------------------------------------------------------
fields:	.ASCII	"LINK SP0 SS0 SP1 SS1 SP2 SS2"	; array of names
	.ASCII	" CR3 EIP EFL EAX ECX EDX EBX ESP EBP ESI EDI"
	.ASCII	"  ES  CS  SS  DS  FS  GSLDTR"
count:	.WORD	( * - fields )/4		; count of names
hexlst:	.ASCII	"0123456789ABCDEF"		; table of digits
outbuf:	.ASCII	" nnn=xxxxxxxx "		; output buffer
outlen:	.WORD	* - outbuf			; output length
;-----------------------------------------------------------------
eax2hex: ; converts value in EAX to a string of hex digits at DS:DI

	pushad				; preserve cpu registers
	mov	edx, eax		; copy AX value to DX
	mov	bx, #hexlst		; BX = xlat-table offset
	mov	cx, #8			; number of nybbles/word
.L0:	rol	edx, #4			; get next nybble in DL
	mov	al, dl			; nybble's byte to AL
	and	al, #0x0F		; isolate bits of nybble
	xlat				; convert to hex numeral
	mov	[di], al		; copy numeral to buffer
	inc	di			; advance buffer-pointer
	loop	.L0			; do rest of the nybbles
	popad				; restore cpu registers
	ret				; back to calling routine
;-----------------------------------------------------------------
;-----------------------------------------------------------------
buf2screen: ; draws output-buffer to screen at address in ES:DI

	lea	si, outbuf		; point DS:SI to string
	cld				; do forward processing
	mov	ah, #0x50		; load color-attributes
	mov	cx, outlen		; setup string's length
.L1:	lodsb				; fetch next character
	stosw				; store char and colors
	loop	.L1			; again for other chars
	ret				; back to calling routine
;-----------------------------------------------------------------
show_task_state_info:
	
	; This procedure draws TSS1 and TSS2 onto the screen

	mov	ax, #sel_vs		; address video memory
	mov	es, ax			;   with ES register
	mov	ax, #sel_ss		; address this segment
	mov	ds, ax			;   with DS register

	xor	dx, dx			; start counting from 0
.L2:
	; move next TSS field-name into the output-buffer
	imul	si, dx, #4		; compute name's offset
	mov	eax, fields[si]		; get the name into EAX
	mov	outbuf, eax		; copy name into buffer

	; compute offset of next field in the TSS
	imul	bx, dx, #4		; compute field's offset

	; draw next field from TSS#1 on the screen
	mov	eax, myTSS1[bx]		; get field-value in AX
	lea	di, outbuf+5		; point to buffer-offset
	call	eax2hex			; convert to hex string
	imul	di, dx, #160		; screen-row's offset
	add	di, #60			; plus column's offset
	call	buf2screen		; draw the output-buffer

	; draw next field from TSS#2 on the screen
	mov	eax, myTSS2[bx]		; get field-value in AX
	lea	di, outbuf+5		; point to buffer-offset
	call	eax2hex			; convert to hex string
	imul	di, dx, #160		; screen-row's offset
	add	di, #120		; plus column's offset
	call	buf2screen		; draw the output-buffer

	; update the loop-counter and check for exit-condition
	inc	dx			; increment field-counter
	cmp	dx, count		; final field was shown?
	jb	.L2			; no, show another field

	ret				; back to 'main' routine
;-----------------------------------------------------------------
	END