;===========================================================================
;
;                                   smt
;
;===========================================================================

;---------------;
; max constants ;
;---------------;

MAX_STACK	equ 512 ;number of words in each stack
MAX_TASKS	equ 8

;-------------;
; task states ;
;-------------;

STATE_FREE	equ 0 ;dead, available task slot
STATE_READY	equ 1 ;running, no ipc activity
STATE_SEND	equ 2 ;sending an ipc message
STATE_RECV	equ 3 ;awaiting an ipc message
STATE_SREQ	equ 4 ;requesting an ipc service

;------------;
; task stack ;
;------------;

TSS_SIZE	equ 24 ;structure size (not on stack)
TSS_FLAGS	equ 22
TSS_CS		equ 20
TSS_IP		equ 18
TSS_AX		equ 16
TSS_BX		equ 14
TSS_CX		equ 12
TSS_DX		equ 10
TSS_SI		equ 8
TSS_DI		equ 6
TSS_BP		equ 4
TSS_DS		equ 2
TSS_ES		equ 0

;---------------;
; service types ;
;---------------;

SERVICE_NONE	equ 0 ;intermediate service (Ex: idle,console,hello)
SERVICE_GETC	equ 1 ;character input service (Ex: stdin)
SERVICE_PUTC	equ 2 ;character output service (Ex: stdout)

;-------------;
; irq offsets ;
;-------------;

IRQ0_IP 	equ (08h*4)
IRQ0_CS 	equ (08h*4+2)

;===========================================================================
; boot entry point
;===========================================================================

use16
org 7c00h

	;-------------------;
	; setup known cs:ip ;
	;-------------------;

	jmp 0:start

;===========================================================================
; kernel entry point
;===========================================================================

start:	;cs:ip = 0x0000:0x7c05

	;--------------------;
	; disable interrupts ;
	;--------------------;

	cli

	;---------------------;
	; init kernel context ;
	;---------------------;

	mov ax,cs
	mov ds,ax
	mov es,ax
	mov ss,ax
	mov sp,7c00h

	;----------------;
	; zero .bss data ;
	;----------------;

	cld
	mov di,BSS_START
	mov cx,BSS_SIZE
	;al = 0
	rep stosb

	;-----------------;
	; chain into irq0 ;
	;-----------------;

	mov ax,[ds:IRQ0_IP]		;get old ip
	mov [ds:old_irq0_ip],ax 	;store old ip
	mov [ds:IRQ0_IP],word irq0	;write new ip

	mov ax,[ds:IRQ0_CS]		;get old cs
	mov [ds:old_irq0_cs],ax 	;store old cs
	mov [ds:IRQ0_CS],cs		;set new cs

	;--------------------;
	; start system tasks ;
	;--------------------;

	mov ax,server_idle	;task entry point offset
	mov cl,SERVICE_NONE	;services the task can provide
	call sys_exec		;input: ax = offset, cl = services
				;output: bx = handle

	mov ax,server_console	;task entry point offset
	mov cl,SERVICE_NONE	;services the task can provide
	call sys_exec		;input: ax = offset, cl = services
				;output: bx = handle

	;---------------------------------;
	; map kernel into idle task space ;
	;---------------------------------;

	mov [ds:active_task],MAX_TASKS*2-2 ;first task handle
	mov sp,MAX_STACK*2*MAX_TASKS+task_stacks

	sti ;enable scheduler
;       jmp server_idle

;===========================================================================
; system servers
;===========================================================================

;---------------------------------------------------------------------------

server_idle:

	hlt
	jmp server_idle

;---------------------------------------------------------------------------

server_video:

	;-----------------------------;
	; await video service request ;
	;-----------------------------;

  .await_request:

	call sys_recv		;out: bx = handle, dx = message, ax = packet

	;------------------------------;
	; handle video service request ;
	;------------------------------;

	cmp dx,SERVICE_PUTC	;only service we provide
	jne .await_request	;go wait for more work

	;---------------------------------------;
	; provide teletype video output service ;
	;---------------------------------------;

  .on_putc:

	mov ah,0eh		;teletype output
	mov bh,0		;video page number

	;handle backspace characters properly

	cmp al,8
	jne .do_it
	int 10h 		;BIOS video service
	mov al,32		;print a space char
	int 10h 		;BIOS video service
	mov al,8

  .do_it:

	int 10h 		;BIOS video service

	;compliment carriage-return with line-feed

	sub al,3
	cmp al,10
	je .do_it

	jmp .await_request	;go wait for more work

;---------------------------------------------------------------------------

server_keyboard:

	;--------------------------------;
	; await keyboard service request ;
	;--------------------------------;

  .await_request:

	call sys_recv		;out: bx = handle, dx = message, ax = packet

	;---------------------------------;
	; handle keyboard service request ;
	;---------------------------------;

	cmp dx,SERVICE_GETC	;only service we provide
	jne .await_request	;go wait for more work

	;--------------------------------;
	; provide keyboard input service ;
	;--------------------------------;

  .on_getc:

	;check keyboard buffer for keystroke

	mov ah,01h		;check keystroke
	int 16h 		;BIOS keyboard service

	;if there is no keystroke available we wait for one

	jz .on_getc_wait

	;remove keystroke from keyboard buffer

	xor ah,ah		;get keystroke
	int 16h 		;BIOS keyboard service

	;send service request reply with data packet

	call sys_send		;in: bx = handle, dx = message, ax = packet

	jmp .await_request	;go wait for more work

  .on_getc_wait:

	;an input request is still pending

	;TODO: It would be better here to give up our time slice to next task.

	hlt		;dont poll continuously
	jmp .on_getc	;try again next schedule

;---------------------------------------------------------------------------

server_console:

	;--------------------;
	; start video server ;
	;--------------------;

	mov ax,server_video		;code entry point offset
	mov cl,SERVICE_PUTC		;services the task can provide
	call sys_exec			;in: ax = offset, cl = services
					;out: bx = handle

	;TODO: Verify task handle and try agin later if no slots available.
	;      This should never happen since its the third task started.

	mov [ds:console_stdout],bx	;store stdout handle

	;-----------------------;
	; start keyboard server ;
	;-----------------------;

	mov ax,server_keyboard		;code entry point offset
	mov cl,SERVICE_GETC		;services the task can provide
	call sys_exec			;in: ax = offset, cl = services
					;out: bx = handle

	;TODO: Verify task handle and try agin later if no slots available.
	;      This should never happen since its the fourth task started.

	mov [ds:console_stdin],bx	;store stdin handle

	;--------------------------;
	; start hello world server ;
	;--------------------------;

  .say_hello:

	mov ax,server_hello		;task entry point offset
	mov cl,SERVICE_NONE		;services the task can provide
	call sys_exec			;in: ax = offset, cl = services
					;out: bx = handle

	;TODO: Verify task handle and try agin later if no slots available.
	;      At the moment we just ignore the request and go on living.

	;--------------------------------;
	; acquire and process user input ;
	;--------------------------------;

  .wait_input:

	;send input service request message to keyboard server

	mov dx,SERVICE_GETC		;service request
	mov bx,[ds:console_stdin]	;send to stdin
	call sys_send			;in: bx = handle, dx = message

	;wait for keyboard server to reply

	call sys_recv			;out: bx = handle, dx = message, ax = packet

	;TODO: Verify reply service by testing handle in bx (should be equal to service_stdin)

	;an 'H' character spawns new hello world application

	cmp al,'H'
	je .say_hello

	;any other data gets sent to stdout server

	mov dx,SERVICE_PUTC		;service request
	mov bx,[ds:console_stdout]	;send to stdout
	call sys_send			;in: bx = handle, dx = message, ax = packet

	jmp .wait_input ;go wait for more work

;---------------------------------------------------------------------------

str_hello db 'Hello World!',0

server_hello:

	;--------------------------------------;
	; display hello world message and quit ;
	;--------------------------------------;

	mov si,str_hello

  .print:

	lodsb

	cmp al,0
	je .done

	;This task has no idea of who provides video services
	;so we must ask the system to try and find a handler.

	mov dx,SERVICE_PUTC		;service request
	call sys_sreq			;in: dx = message, ax = packet
					;out: bx = handle, dx = message, ax = packet

	;If no video server was found, quit this task.

	or bx,bx			;compare bx to -2
	jns .print

  .done:

	;This task exits upon completion of displaying its message.

;       call sys_exit   ;never returns

;===========================================================================
; system api
;===========================================================================

;---------------------------------------------------------------------------
; sys_exit -- exits a task
; in: no parameters
; out: never returns
; uses: bx
;---------------------------------------------------------------------------

sys_exit:

	mov bx,[ds:active_task]
	mov byte[ds:bx+task_states],STATE_FREE
	jmp $

;---------------------------------------------------------------------------
; sys_exec -- starts a new task
; in: ax = task offset, cl = ipc services
; out: bx = task handle (-2 = out of task slots)
; uses: di,dx
; notes: If no test slots are available it is up to the caller to try again
;        later, rather than hanging it while waiting for an open slot.
;---------------------------------------------------------------------------

sys_exec:

	pushf	;disable interrupts only within this context
	cli

	mov bx,MAX_TASKS*2 ;allocate task handles from top down
	mov di,MAX_TASKS*2*MAX_STACK+task_stacks+MAX_STACK*2-TSS_SIZE ;allocate task stacks from top down

  .find_free_slot:

	sub di,MAX_STACK*2

	dec bx
	dec bx
	js .no_free_slot ;-2 = no task slots available

	cmp byte[ds:bx+task_states],STATE_FREE
	jne .find_free_slot

	mov [ds:bx+stack_ptrs],di

	pushf
	pop dx
	or dx,512 ;'IRQ enable' bit in the flags register

	mov [ds:di+TSS_FLAGS],dx
	mov [ds:di+TSS_CS],cs
	mov [ds:di+TSS_IP],ax
;       mov [ds:di+TSS_AX],0   ;no need to initialize these
;       mov [ds:di+TSS_BX],0
;       mov [ds:di+TSS_CX],0
;       mov [ds:di+TSS_DX],0
;       mov [ds:di+TSS_SI],0
;       mov [ds:di+TSS_DI],0
;       mov [ds:di+TSS_BP],0
	mov [ds:di+TSS_DS],cs
	mov [ds:di+TSS_ES],cs

	mov byte[ds:bx+ipc_services],cl
	mov byte[ds:bx+task_states],STATE_READY

  .no_free_slot:

	popf
	ret

;---------------------------------------------------------------------------
; sys_send -- sends an ipc message
; in: bx = ipc target handle, dx = message to send, ax = data packet to send
; out: no parameters
; uses: bp,cl
;---------------------------------------------------------------------------

sys_send:

	mov cl,STATE_SEND
	jmp ipc_squak

;---------------------------------------------------------------------------
; sys_recv -- receives an ipc message
; in: no parameters
; out: bx = source handle, dx = message to recv, ax = data packet to recv
; uses: bp,cl
;---------------------------------------------------------------------------

sys_recv:

	mov cl,STATE_RECV
	jmp ipc_squak

;---------------------------------------------------------------------------
; sys_sreq -- requests an ipc service
; in: dx = message, ax = data packet
; out: bx = ipc service handle (-2 = no handler found)
; uses: bp,cl
;---------------------------------------------------------------------------

sys_sreq:

	mov cl,STATE_SREQ
;       jmp ipc_squak

;---------------------------------------------------------------------------
; ipc_squak -- setup an ipc connection
; in: bx = handle, dx = message, ax = packet, cl = new state
; out: bx = handle, dx = message, ax = packet
; uses: bp
;---------------------------------------------------------------------------

ipc_squak:

	mov bp,[ds:active_task]

	mov [ss:bp+ipc_links],bx
	mov [ss:bp+ipc_messages],dx
	mov [ss:bp+ipc_packets],ax

	mov byte[ss:bp+task_states],cl

  .wait:

	cmp byte[ss:bp+task_states],STATE_READY
	jne .wait

	mov bx,[ss:bp+ipc_links]
	mov dx,[ss:bp+ipc_messages]
	mov ax,[ss:bp+ipc_packets]

	ret

;===========================================================================
; IRQ0 hook
;===========================================================================

irq0:

	;--------------------;
	; disable interrupts ;
	;--------------------;

	cli

	;----------------------;
	; store active context ;
	;----------------------;

	push ax
	push bx
	push cx
	push dx
	push si
	push di
	push bp
	push ds
	push es

	;---------------------;
	; load kernel context ;
	;---------------------;

	mov ax,cs
	mov ds,ax
	mov es,ax
;       mov ss,ax

	;----------------;
	; load task info ;
	;----------------;

	mov bx,[ds:active_task]
	mov ax,[ds:bx+task_states]
	mov di,[ds:bx+ipc_links]
	mov cx,[ds:bx+ipc_messages]
	mov dx,[ds:bx+ipc_packets]

	;----------------------------;
	; handle ipc service request ;
	;----------------------------;

	cmp al,STATE_SREQ
	jne .not_sreq

	mov di,MAX_TASKS*2

  .find_service_handler:

	dec di
	dec di
	js .no_service_handler

	cmp [ds:di+ipc_services],cx ;can he handle this type of service request message
	jne .find_service_handler   ;keep looking for someone who can

	mov [ds:bx+ipc_links],di ;he can do it, so lets link ourself to him
	jmp .ipc_send		 ;send package it to him

  .no_service_handler:

	mov [ds:bx+ipc_links],di ;no handler found so return -2
	jmp .task_done ;stop trying to post service request

  .not_sreq:

	;----------------------------;
	; handle ipc message passing ;
	;----------------------------;

	cmp al,STATE_SEND
	jne .next_task

  .ipc_send:

	cmp byte[ds:di+task_states],STATE_RECV ;is target in recv mode?
	jne .next_task ;if not, try again later

  .ipc_copy:

	mov [ds:di+ipc_links],bx	;tell target who sent it (us)
	mov [ds:di+ipc_messages],cx	;put message in his mailbox
	mov [ds:di+ipc_packets],dx	;put data in his mailbox

	mov byte[ds:di+task_states],STATE_READY ;take target out of recv mode (break his wait loop)

  .task_done:

	mov byte[ds:bx+task_states],STATE_READY ;take us out of send/sreq mode

  .next_task:

	;------------------------;
	; perform task sheduling ;
	;------------------------;

	;BX = active task handle

	;The sheduler requires an idle task whith constant READY state to work properly.
	;Otherwise we could end up in and endless loop trying to find a task to shedule.

	mov [ds:bx+stack_ptrs],sp

  .find_work:

	dec bx
	dec bx
	jns .check_state

	mov bx,MAX_TASKS*2-2 ;wrap around

  .check_state:

	cmp byte[ds:bx+task_states],STATE_FREE
	je .find_work ;dont schedule dead tasks

	cmp byte[ds:bx+task_states],STATE_RECV
	je .find_work ;dont schedule waiting tasks

	mov [ds:active_task],bx ;new active task handle

	;-------------------;
	; load task context ;
	;-------------------;

	mov sp,[ds:bx+stack_ptrs]

	pop es
	pop ds
	pop bp
	pop di
	pop si
	pop dx
	pop cx
	pop bx
	pop ax

	;---------------------------;
	; jump far into old handler ;
	;---------------------------;

	    db 0eah  ;opcode: jmp far
old_irq0_ip dw 0000h ;hot-patch: ip
old_irq0_cs dw 0000h ;hot-patch: cs

;===========================================================================
; boot code finalization
;===========================================================================

db (510-($-$$)) dup(0)	;currently at 495 bytes of code :)

dw 0aa55h

;===========================================================================
; uninitialized data
;===========================================================================

BSS_START:

  active_task		rw 1
  task_stacks		rw MAX_TASKS*MAX_STACK
  stack_ptrs		rw MAX_TASKS
  task_states		rw MAX_TASKS ;only lobyte is used
  ipc_services		rw MAX_TASKS ;only lobyte is used
  ipc_messages		rw MAX_TASKS
  ipc_links		rw MAX_TASKS
  ipc_packets		rw MAX_TASKS
  ;console server private data
  console_stdin 	rw 1
  console_stdout	rw 1

BSS_SIZE = $ - BSS_START

;===========================================================================
