;===========================================================================
;
;                                   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

;===========================================================================
