/*
 * Copyright (c) 1990, 1999 Erick Engelke
 */
// #define USE_FP   /* uncomment this if you want FP emulation allowed */
                    /* but it will remove the safety signature on your stack */

/**************************************************************************/
/* INCLUDES                                                               */
/**************************************************************************/
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <cpujmp.h>
#include <dos.h>
#if defined( __BORLANDC ) || defined( __TURBOC__ )
#include <alloc.h>
#else
#include <malloc.h>
#endif

#include <mem.h>
#include <bios.h>
#include <string.h>

#if defined(__DJGPP__)
#include <sys/time.h>
#include <sys/farptr.h>
#include <signal.h>
#include <go32.h>
#include <dpmi.h>
#endif

#include "rtos.h"


/**************************************************************************/
/* FLAGS                                                                  */
/**************************************************************************/

/*
 * DJGPP stack does not allow us to place signature at SS:0
 */
#if defined(__DJGPP__)
#define USE_FP
#endif

#define DEBUG 1

#define USE_INTR

#include <conio.h>

/**************************************************************************/
/* DEFINES                                                                */
/**************************************************************************/

#define DIM(x)      (sizeof(x) / sizeof((x)[0]))

#define THS_THREAD  0x0001  /* is a thread */
#define THS_SLEEP   0x0002  /* see th_waketime */
#define THS_SYNC    0x0004  /* see th_syncptr */

#define STACK_SIG   (('R' << 0) + ('T' << 8) + ('O' << 16) + ('S' << 24))

#define KFASTBLOCK()   kblocked++
#define KFASTUNBLOCK() kblocked--

#define OCW1    0x21    /* 8259 */
#define OCW2    0x20
#define OCW1_2  0xA1    /* 8259 */
#define OCW2_2  0xA0

/**************************************************************************/
/* VARIABLES                                                              */
/**************************************************************************/

thread_x *kmainthread = NULL;  /* the main thread pointer */
thread_x *krun = NULL;  /* running list */
thread_x *kwait = NULL; /* waiting list */
thread_x *kdye = NULL; /* dying list */
msg_x *kfreemessages = NULL;
int volatile kinisr = 0;   /* ISR active, don't do thread switch */
int kcheckstacksig = 1;    /* check stack signature within ISR */
int _fastdos = 1;       /* set to zero if we wish to use CS's rather than
                         * kblock()'s around DOS calls
                         */
crit_x *_dos_cs = NULL;
int kusewindows = 0;

int kdebug = 0;

/*------------------------------------*/
#define TIME_X_TOP 37287L
#define TIME_X_BOT 2048L

DWORD ktime = 0L;    /* system timer */
WORD ktime2 = 0;
DWORD kupticks = 0;


#if defined( __BORLANDC__ ) || defined( __TURBOC__ )
static DWORD far *sysclock = 0x46cL;    /* PC Bios clock */
static BYTE far *sysoverflow = 0x470L;   /* clock went past midnight */

#elif defined(__DJGPP__)
static DWORD dummyclock;    /* until real clock is assigned */
static DWORD *sysclock = &dummyclock;

#endif


static int   kirqscount = 0;
static int   kirqsper = 0;
DWORD kseconds = 0;
/*------------------------------------*/

static DWORD ktime0 = 0L;    /* used if frequency is higher than 1kHz */
DWORD kincr = 55;  /* ms frequency */
DWORD kincr0 = 0;


static long ksinceIRQ8 = 0;
thread_x *kcurthread;
WORD kpreemptive = 0;
volatile WORD kblocked = 0;

void (*k_user_int8)(void) = NULL;

int kctrlbreak = 1;


/**************************************************************************/
/* CODE                                                                   */
/**************************************************************************/

/*--------------------------------------------------------------------*/
/* debugging stuff                                                    */
/*--------------------------------------------------------------------*/
void k_debugtest( void )
{
    if ( bioskey( 1 ) ) {
        puts("\ndebugging stopped");
        exit( 3 );
    }
}



void kblock(void)
{
    kblocked++;
}

void kunblock(void)
{
    if ( kblocked > 0 ) kblocked--;
}


void rt_cpu_block( WORD *p )
{
#if defined( __TURBOC__ ) || defined( __BORLANDC__ )
    *p = _FLAGS;    /* save interrupt state */
    disable();      /* disable interrupts for a spell */
#elif defined( __DJGPP__ )
    __asm__ __volatile__ (
        "pushfl\n\t"
        "popl  %%eax\n\t"
        "cli\n\t"
        : "=a" (*p));
#endif
}

void rt_cpu_unblock( WORD *p )
{
#define I86_INTERRUPT_ENABLE_FLAG 0x200
    if ( *p & I86_INTERRUPT_ENABLE_FLAG )
        enable();
}


int rt_thread_status( thread_x *t )
{

    int result;


    KFASTBLOCK();
    if ( (t == kcurthread) || ( t==NULL ) )
        result = TSTAT_RUNNING;
    else if ( t->th_head == &krun )
        result = TSTAT_WILL_RUN;
    else if ( t->th_head == &kwait )
        result = TSTAT_WAITING;
    else if ( t->th_critical != NULL )
        result = TSTAT_CRITICAL;

    else
        result = TSTAT_DEAD;
    KFASTUNBLOCK();
    return( result );

}

void rt_halt( char *msg )
{
    KFASTBLOCK();
    if ( kusewindows ) {
        gotoxy( 1, 25 );
        clreol();
    }

    printf("HALT in %s: %s\n", kcurthread->th_name, msg ? msg : "no message specified");
#ifdef __DJGPP__
    sio_exit();
    rt_restoretimer();
    abort();   /* produce traceback */
#endif
    exit( 3 );
}

/* internal routine to remove thread from a list */
static void kremthread(  thread_x *t )
{
    thread_x *suc, *pred;
    WORD flags;
    thread_x **head;

    if ( t == NULL ) t = kcurthread;

    rt_cpu_block( &flags );
    head = t->th_head;
    suc = t->th_next;
    pred = t->th_prev;

    /* deal with successor */
    if ( suc != NULL )
        suc->th_prev = pred;

    /* deal with predecssor */
    if ( pred != NULL )
        pred->th_next = suc;
    else
        *head = suc;

    t->th_head = NULL;

    rt_cpu_unblock( &flags );
}
/* add this to new list */
void kaddthread( thread_x **head, thread_x *t, int howsort )
{
    thread_x *x, *prev;
    WORD flags;
    BYTE ourpriority;

    rt_cpu_block( &flags );

    x = *head;
    prev = NULL;

    do {
        if ( x == NULL ) break;     /* insert it here */

        /* there is at least one existing entry */
        if ( howsort == TS_NOSORT )
            break;             /* insert it here */

        /* skip past higher priorities */
        if ( howsort == TS_PRIORITY ) {
            ourpriority = t->th_priority;
            /* at start, x is pointing at head */
            do {
                if ( x->th_priority <= ourpriority ) {
                    /* skip past these more important things */
                    prev = x;
                    x = x->th_next;
                    /* until we hit end of list */
                    if ( x == NULL ) break;
                } else
                    break;
            } while ( 1 );
            /* insert here */
            break;
        }

        /* sort by timer */
        if ( howsort == TS_SOON ) {
            do {
                if (( x->th_waketime < t->th_waketime ) &&
                    ( x->th_waketime2 < t->th_waketime2 )) {

                    prev = x;
                    x = x->th_next;
                    if ( x == NULL ) break;
                } else
                    break;
            } while ( 1 );
            /* insert here */
            break;
        }
    } while ( 0 );

    /* x = where_to_insert_before or NULL=end, prev = prev or NULL/head */
    t->th_prev = prev;
    if ( prev ) prev->th_next = t;
    else *head = t;
    t->th_next = x;
    if ( x != NULL )
        x->th_prev = t;
    t->th_head = head;
    rt_cpu_unblock( &flags );
    return;
}

/*--------------------------------------------------------------------*/
/* critical sections                                                  */
/*--------------------------------------------------------------------*/
crit_x *cs_alloc( void )
{
    return( kcalloc( sizeof( crit_x ), 1 ));
}

void cs_new( crit_x **cs )
{
    kblock();
    if ( *cs == NULL )
        if ( (*cs = cs_alloc()) == NULL )
            rt_halt("out of memory on cs_new()");
    kunblock();
}
void cs_free( crit_x *x )
{
    kfree( x );
}

void cs_enter( crit_x *cs )
{
    WORD flags;

    if ( cs != NULL ) {
        rt_cpu_block( &flags );
        if ( (cs->cs_active == NULL ) || (cs->cs_active == kcurthread )) {
            /* we take it */
            cs->cs_active = kcurthread;
            kcurthread->th_critical = cs;
            rt_cpu_unblock(&flags);
        } else {
            /* we need to wait for this */
            kremthread( kcurthread );       /* stop running */
            kaddthread( &cs->cs_blocked, kcurthread , TS_PRIORITY);
            kcurthread->th_critical = cs;
            rt_cpu_unblock( &flags );
            rt_yield(); /* and go to the safe one */
        }
        cs->cs_depth ++;
    }
}
void cs_exit( crit_x *cs )
{
    thread_x *t;
    WORD flags;


    if ( cs != NULL ) {
        rt_cpu_block(&flags);
        cs->cs_depth --;
        if ( cs->cs_depth == 0 ) {
            kcurthread->th_critical = NULL;
            if ( cs->cs_active == kcurthread ) {
                t = cs->cs_blocked;
                if ( t == NULL )
                    /* nothing waiting */
                    cs->cs_active = NULL;
                else {
                    /* move first waiting to next executing */
                    kremthread( t );
                    cs->cs_active = t;
                    kaddthread( &krun, t, TS_PRIORITY );
                    rt_cpu_unblock( &flags );
                    rt_yield();     /* go to it */
                    return;
                }
            }
        }
        rt_cpu_unblock(&flags);
    }
}

void dos_enter( void )
{
    if ( _fastdos ) {
        KFASTBLOCK();
    } else {
        cs_enter( _dos_cs );
    }
}
void dos_exit( void )
{
    if ( _fastdos ) {
        KFASTUNBLOCK();
    } else {
        cs_exit( _dos_cs );
    }
}
/*--------------------------------------------------------------------*/
/* memory allocation                                                  */
/*--------------------------------------------------------------------*/

DWORD kcorefree( void )
{
    DWORD d;

#if defined(__TURBOC__) || defined(__BORALNDC__)
    dos_enter();
    d = coreleft();

#elif defined( __DJGPP__)
    __dpmi_free_mem_info mem;

    dos_enter();
    __dpmi_get_free_memory_information (&mem);
    d = 4096 * mem.total_number_of_free_pages;
#endif

    dos_exit();
    return( d );
}
void *kcalloc( WORD n, WORD size )
{
    void *p;

#ifdef OLD
    dos_enter();
    p = calloc( n, size );
    dos_exit();
    return( p );
#else
    WORD sz;
    mem_x *m;

    sz = n * size;
    dos_enter();
    m = calloc( 1, sz + sizeof( mem_x ) + sizeof( DWORD ));
    dos_exit();
    m->mem_size = sz;
    m->mem_startsig = MEM_STARTSIG;
    m->mem_thread = kcurthread;
    *(DWORD*) &m->mem_data [sz] = MEM_ENDSIG;
    return ( &m->mem_data[0] );
#endif
}
void kfree( void *p )
{
#ifdef OLD
    dos_enter();
    if ( p != NULL )
        free( p );
    dos_exit();
#else
    mem_x *m;
    DWORD *dw;

    m = (mem_x*) ((DWORD)p - offsetof(mem_x, mem_data));
    if ( m->mem_startsig != MEM_STARTSIG ) {
        char buf[ 256];
        sprintf( buf, "kfree of mangled mem (or not kcalloc'd) %08lx", (DWORD)p );
        if ( kdebug > 1 ) {
            cputs( buf );
            cputs( "\r\ncontinuing without freeing buffer\r\n");
            return;
        }
        rt_halt( buf );
    }
    dw = (DWORD*) &m->mem_data [m->mem_size];
    if ( *dw != MEM_ENDSIG ) {
        thread_x *th;
        char buf[ 256 ];
        th = m->mem_thread;
        sprintf( buf, "mem overflow @ %08lx, kcalloced in %s",
                 (DWORD)&m->mem_data, th->th_name );
        if ( kdebug > 1 ) {
            cputs( buf );
            cputs( "\r\ncontinuing without freeing buffer\r\n");
            return;
        }
        rt_halt( buf );
    }
    dos_enter();
    free( m );
    dos_exit();
#endif
}

void *krealloc( void *m, WORD size )
{
    void *p;
    dos_enter();
    p = realloc( m, size );
    dos_exit();
    return( p );
}

/*--------------------------------------------------------------------*/
/* messaging stuff                                                    */
/*--------------------------------------------------------------------*/

/*
 * kfreeallmessages - used to free all pending messages at task end time
 */
static void kfreeallmessages( thread_x *t )
{
    msg_x *firstm, *lastm, *p;
    WORD flags;

    firstm = t->th_messagelist;
    if ( firstm != NULL ) {
        lastm = firstm;
        while ( (p = lastm->m_next )!=NULL) lastm = p;

        rt_cpu_block( &flags );
        lastm->m_next = kfreemessages;
        kfreemessages = firstm;
        rt_cpu_unblock( &flags );
        t->th_messagelist = NULL;
    }
}
/*--------------------------------------------------------------------*/
void kmessage_on_exit( void *thread, int value, DWORD data )
{
    thread_x *t;

    /* most likely send it to parent */
    if ( thread == NULL ) t = kcurthread->th_parent;
    else t = (thread_x*)thread;

    kcurthread->th_end_thread = t;
    kcurthread->th_end_value = value;
    kcurthread->th_end_data = data;
}

#ifndef FIXMESSAGE
static void ksendmessagebuffer( thread_x* t, int value, DWORD data, msg_x* m)
{
    msg_x *mm;
    if (( t->th_messagewait == value ) &&( value != 0) ) {
        t->th_messagewait = 0;  // cancel the wait
        t->th_status &= ~THS_MESG;
        kresume( t );
    } else {
        kfreemessages = m->m_next;  /* advance to next free one */

        m->m_value = value;
        m->m_data = data;
        m->m_next = NULL;

        /* add to end of list */
        if ( t->th_messagelist == NULL ) t->th_messagelist = t->th_messagetail = m;
        else if ( t->th_messagetail ) {
            t->th_messagetail->m_next = m;
            t->th_messagetail = m;
        } else {
            mm = t->th_messagelist;
            while ( mm->m_next )
                mm = mm->m_next ;
            t->th_messagetail = mm->m_next = m;
        }
        if ( t->th_status & THS_MESG ) {
            /* it is blocked waiting */
            /* see if waiting for general message or specialized one */
            if ( t->th_messagewait == 0 ) {
                t->th_status &= ~THS_MESG;
                kresume( t );
            }
        }
    }
}

int ksendmessage( void *thread , int value, DWORD data )
{
    msg_x *m;
    WORD flags;

    rt_cpu_block(&flags);
    m = kfreemessages;
    if ( m != NULL)
      ksendmessagebuffer( thread, value, data, m);
    rt_cpu_unblock(&flags);
    return( m ? 1 : 0 );        // 2000.9.13 added return value
}

void kwritemessage( void *thread , int value, DWORD data )
{
    msg_x *m;
    WORD flags;

    rt_cpu_block(&flags);
    do {
        m = kfreemessages;
        if ( m != NULL ) break;

        rt_cpu_unblock(&flags);
        rt_yield(); /* wait for a free one */
        rt_cpu_block(&flags);
    } while ( m == NULL );

    ksendmessagebuffer( thread, value, data, m);
    rt_cpu_unblock(&flags);
}

#else  /* old messaging */

void ksendmessage( void *thread , int value, DWORD data )
{
    msg_x *m, *mm;
    thread_x *t;
    WORD flags;

    t = (thread_x*) thread;

    rt_cpu_block(&flags);
    m = kfreemessages;
    if ( m == NULL ) {
        rt_cpu_unblock(&flags);
        return;
    }

    if (( t->th_messagewait == value ) &&( value != 0) ) {
        t->th_messagewait = 0;  // cancel the wait
        t->th_status &= ~THS_MESG;
        kresume( t );
    } else {
        kfreemessages = m->m_next;  /* advance to next free one */

        m->m_value = value;
        m->m_data = data;
        m->m_next = NULL;

        /* add to end of list */
        if ( t->th_messagelist == NULL ) t->th_messagelist = t->th_messagetail = m;
        else if ( t->th_messagetail ) {
            t->th_messagetail->m_next = m;
            t->th_messagetail = m;
        } else {
            for ( mm = t->th_messagelist; mm->m_next ; mm = mm->m_next );
            t->th_messagetail = mm->m_next = m;
        }
        if ( t->th_status & THS_MESG ) {
            /* it is blocked waiting */
            /* see if waiting for general message or specialized one */
            if ( t->th_messagewait == 0 ) {
                t->th_status &= ~THS_MESG;
                kresume( t );
            }
        }
    }
    rt_cpu_unblock(&flags);
}

void kwritemessage( void *thread , int value, DWORD data )
{
    msg_x *m, *mm;
    thread_x *t;
    WORD flags;

    t = (thread_x*) thread;

    rt_cpu_block(&flags);
    do {
        m = kfreemessages;
        if ( m != NULL ) break;

        rt_cpu_unblock(&flags);
        rt_yield(); /* wait for a free one */
        rt_cpu_block(&flags);
    } while ( m == NULL );

    kfreemessages = m->m_next;  /* advance to next free one */

    m->m_value = value;
    m->m_data = data;
    m->m_next = NULL;

    /* add to end of list */
    if ( t->th_messagelist == NULL ) t->th_messagelist = t->th_messagetail = m;
    else if ( t->th_messagetail ) {
        t->th_messagetail->m_next = m;
        t->th_messagetail = m;
    } else {
        for ( mm = t->th_messagelist; mm->m_next ; mm = mm->m_next );
        t->th_messagetail = mm->m_next = m;
    }
    if ( t->th_status & THS_MESG ) {
        /* it is blocked waiting */
        t->th_status &= ~THS_MESG;
        kresume( t );
    }

    rt_cpu_unblock(&flags);
}
#endif
/* non-blocking version */
int kgetmessage( int *value, DWORD *data )
{
    msg_x *m;
    WORD flags;

    rt_cpu_block( &flags );
    if ( (m = kcurthread->th_messagelist) != NULL ) {
        *value = m->m_value;
        *data = m->m_data;
        kcurthread->th_messagelist = m->m_next;
        if ( kcurthread->th_messagetail == m )
            kcurthread->th_messagetail = NULL;

        /* return it to the freelist */
        m->m_next = kfreemessages;
        kfreemessages = m;
//      kfree( (BYTE*) m );
        rt_cpu_unblock(&flags);
        return( 1 );
    }
    rt_cpu_unblock(&flags);
    return( 0 );
}

int kreadmessage( int *value, DWORD *data )
{
    WORD flags;
    rt_cpu_block( &flags );
    while ( kcurthread->th_messagelist == NULL ) {
        kcurthread->th_status |= THS_MESG;
        ksuspend(kcurthread);
        rt_cpu_unblock( &flags );
        rt_yield();
    }
    rt_cpu_unblock( &flags );
    return( kgetmessage( value, data ));
}

void kreadspecialmessage( int value )
{
    WORD flags;

    rt_cpu_block( &flags );
    if ( kcurthread->th_messagelist ) {
        msg_x **mm = &kcurthread->th_messagelist;
        msg_x *m = *mm;

        while ( m ) {
            if ( m->m_value == value ) {
                /* found msg, remove it */
                *mm = m->m_next;
                m->m_next = kfreemessages;
                kfreemessages = m;
                rt_cpu_unblock( &flags );
                return;
            }
            mm = &m->m_next;
            m = *mm;
        }
    }

    kcurthread->th_messagewait = value;
    kcurthread->th_status |= THS_MESG;
    ksuspend(kcurthread);
    rt_cpu_unblock( &flags );
    rt_yield();
}

DWORD knumthreads( void )
{
    DWORD i;
    thread_x *t;

    KFASTBLOCK();
    for (i = 0 , t = krun ; t ; t = t->th_next ) {
        cprintf("running : %s\r\n", t->th_name );
        i++;
    }
    for ( t = kwait ; t ; t= t->th_next ) {
        cprintf("waiting : %s\r\n", t->th_name );
        i++;
    }
    KFASTUNBLOCK();
    return( i );
}

void ksuspendhow( thread_x *t, thread_x **newlist )
{
    KFASTBLOCK();
    if ( t == NULL ) t = kcurthread;
    kremthread( t );
    kaddthread( newlist, t, TS_NOSORT );
    KFASTUNBLOCK();
}

void ksuspend( thread_x *t )
{
    KFASTBLOCK();
    if ( t == NULL ) t = kcurthread;
    kremthread( t );
    kaddthread( &kwait, t, TS_PRIORITY );
    KFASTUNBLOCK();
}
void kresume( thread_x *t)
{
    thread_x **head;

    KFASTBLOCK();
    if ( t == NULL ) t = kcurthread;
    if ( t->th_sig != TH_SIG )
        rt_halt("attempt to resume dead thread");

    head = t->th_head;

    /* don't do anything if already running */
    if ( head != &krun ) {
        kremthread( t);
        kaddthread( &krun, t , TS_PRIORITY);
    }
    KFASTUNBLOCK();
}

void kdestroythread( thread_x *t )
{
    bq_str *bq;
    // crit_x *cr;
    WORD flags;

    KFASTBLOCK();
    if ( t == NULL ) t = kcurthread;
    if ( t->th_main ) exit( 0 );

    rt_cpu_block( &flags );

    /* ensure we do not get restarted by byte queue or crit x */
    /* NOT needed, kremthread removes us from this list
     * if ( (cr = t->th_crit) != NULL ) {
     *   if ( cr->cs_blocked == t )
     *       cr->cs_blocked = NULL;
     * }
    */

    if ( (bq = t->th_bq) != NULL ) {
        if ( bq->bq_sendwait == kcurthread ) bq->bq_sendwait = NULL;
        if ( bq->bq_recvwait == kcurthread ) bq->bq_recvwait = NULL;
    }
    rt_cpu_unblock( &flags );

    kremthread( t );
    kaddthread( &kdye, t , TS_NOSORT );
    KFASTUNBLOCK();
    kfreeallmessages( t );  /* return message list to pool */
    rt_nextthread();    /* and we'll never run again */
}
void kfinaldeath( thread_x *t )
{

    t->th_sig = 0;
    kremthread( t );


    if ( t->th_stack )
        kfree( t->th_stack );   /* kill this stack */
    kfree( t );
}

char *kthreadname( void )
{
    return( kcurthread->th_name );
}

#ifdef NEVER
/*
 * sync_test - attempt to set a byte to 1
 */
BYTE sync_test( void *p )
{
    _asm {
        push    es
        push    di
        les     di, p
        mov     ax, 1
        xchg    es:[di], al
        pop     di
        pop     es
    }
    return( _AL );
}
#endif

/*
 * rt_clocktest - test waiting threads for clock reference
 */
void rt_clocktest( void )
{
    WORD st;
    thread_x *t, *next;
//  static DWORD lasttime = 0;
    WORD flags;
//    int status;

    rt_cpu_block( &flags );

    /* do we need to do this ? */
//    if ( lasttime == ktime ) return;

    /* look through inactive list */
    for ( t = kwait ; t ; t = next ) {
        next = t->th_next;  /* learn this now while still valid */
        st = t->th_status;
        if ( st & THS_SLEEP ) {
            WORD oldstatus;
            DWORD oldwaketime;
            WORD oldwaketime2;

            if ( (t->th_waketime2 < ktime2 ) ||
             ( (( t->th_waketime <= ktime ) &&
                ( t->th_waketime2 == ktime2 )))) {

                oldstatus = t->th_status;
                oldwaketime = t->th_waketime;
                oldwaketime2= t->th_waketime2;
                t->th_status &= ~THS_SLEEP;
                t->th_waketime = 0;
                t->th_waketime2 = 0;
                if ( !ksendmessage( t, EMSG_TIMER, 0 )) {
                    t->th_status = oldstatus;
                    t->th_waketime =  oldwaketime;
                    t->th_waketime2 = oldwaketime2;
                }
            }
        }
    }
    rt_cpu_unblock( &flags );
}

void rt_nextthread(void)
{
//  WORD bestpri, testpri, st;
    thread_x *bestt;

//  thread_x *t, *next;
//  int hitcur;
//  WORD flags;

    if ( kblocked > 0 ) {
        return;
    }
    KFASTBLOCK();
    if ( kusewindows ) {
        kcurthread->th_x = wherex();
        kcurthread->th_y = wherey();
    }
    /* save current CPU state - returns 0 and skips this now */
    /* later when we execute cpulongjmp we will return here */
#if defined(__TURBOC__)||defined(__BORLANDC__)
    if ( cpusetjmp( &kcurthread->th_ptr ) ) {
#elif defined(__DJGPP__)
    if ( setjmp( &kcurthread->th_ptr ) ) {
#endif
        /* kill dead tasks */
        while ( kdye )
            kfinaldeath( kdye );

        if ( kcurthread->th_sig != TH_SIG )
            rt_halt("dead thread still in task queue");

        if ( kcurthread != kmainthread ) {
            if ( kcheckstacksig ) {
                if ( *(DWORD*)kcurthread->th_stack != STACK_SIG )
                    rt_halt( "stack overflow - signature");
            }

#if defined(__TURBOC__) || defined(__BORLANDC__)
//          if ( kcurthread->th_ptr.j_sp < FP_OFF( kcurthread->th_stack ))
            if ( kcurthread->th_ptr.j_sp > kcurthread->th_stacklen )
#elif defined(__DJGPP__)
         // if ( 0 )
            if (kcurthread->th_ptr.__esp < (kcurthread->th_stack - kcurthread->th_stacklen))
#endif
                rt_halt( "stack overflow - size");
        }
        if ( kusewindows ) {
            window( kcurthread->th_left,
                kcurthread->th_top,
                kcurthread->th_right,
                kcurthread->th_bottom);
            gotoxy( kcurthread->th_x,
                kcurthread->th_y);
        }
        KFASTUNBLOCK();
        return;
    }

    do {
        if ( kcurthread->th_head != &krun )
            /* go to head of list */
            bestt = krun;
        else {
            /* use next thread */
            bestt = kcurthread->th_next;
            if ( bestt != NULL ) {
                if ( bestt->th_priority > krun->th_priority )
                    bestt = krun;
            } else
                bestt = krun;
        }
        if ( bestt == krun )
            rt_clocktest();

        if ( kdebug > 0 )
            k_debugtest();

        /* run the process with the best priority */
        if ( bestt != NULL ) {
            kcurthread = bestt;
            if ( kcurthread->th_sig != TH_SIG )
                rt_halt( "signature on thread overwritten by application");
            if ( kcurthread->th_ptrsig != TH_SIG )
                rt_halt( "signature error on registers");

#if defined(__TURBOC__)||defined(__BORLANDC__)
            cpulongjmp( &kcurthread->th_ptr , 1 );
#elif defined(__DJGPP__)
            longjmp( &kcurthread->th_ptr, 1 );
#endif
        } else
            rt_clocktest();

    } while ( 1 );
}

void xrt_yield( char *file, int line )
{
  if ( kblocked > 0 ) {
      char buf[ 256 ];
      sprintf( buf, "yield called when kblocked > 0, %s line %u\n", file, line );
      rt_halt( buf );
  }
  rt_nextthread();
}

/*--------------------------------------------------------------------*/
/* timer                                                              */
/*--------------------------------------------------------------------*/
#if defined(__TURBOC__)||defined(__BORLANDC__)
typedef struct _false_int {
    BYTE pushf1;
    BYTE call;
    void interrupt (*isr1)();
    BYTE jmp2;
    void interrupt (*isr2)();
};

struct _false_int false_int = {
    0x9c, 0x9a, NULL,
    0xcf, NULL };
#endif

 /*
  * ktimer()
  */

#if defined(__TURBOC__)||defined(__BORLANDC__)
static void interrupt (*koldtimer)(void) = NULL;
static void interrupt ktimer(void)
#elif defined(__DJGPP__)
static void ktimer( void )
#endif
{
    DWORD oldtime;

#if defined(__TURBOC__) || defined(__BORLANDC__)
    static WORD *screen = MK_FP( 0xb800, 0);

    if (kdebug > 1 )
        *screen = kblocked + 0x0f30;

#elif defined(__DJGPP__)
    if (kdebug > 1)
       _farpokew (_dos_ds, 0xB8000, kblocked + '0' + (15 << 8));
#endif

    /* update eRTOS internal clock */
    if ( kincr != 0 ) {
        oldtime = ktime;
        ktime += kincr;
        if ( ktime < oldtime ) ktime2++;
        ksinceIRQ8 += kincr;
    } else {
        ktime0 += kincr0;
        if ( ktime0 >= 10 ) {
            ktime++;
            if ( ktime == 0 ) ktime2++;
            ksinceIRQ8 ++;
            ktime0 = 0;
        }
    }

//  outportb( 0x20, 0x20 );

    /* see if we should chain to old or send EOI ourselves */
    if ( ksinceIRQ8 >= 55 ) {
        ksinceIRQ8 -= 55;
        kupticks++;
#if defined(__TURBOC__)||defined(__BORLANDC__)
        koldtimer();
#endif
    }
#if defined(__TURBOC__)||defined(__BORLANDC__)
    else
        outportb( 0x20, 0x20 );

    enable();
#endif

    /* adjust count for BIOS clock */
#define END_OF_DAY (24L*60L*60L)
#define LAST_UPDATES (END_OF_DAY - 10L)
    if ( kirqsper ) {
      kirqscount++;       /* clock fixer */
      if ( kirqscount >= kirqsper ) {
          kirqscount -= kirqsper;
          kseconds ++;
          if ( kseconds >= END_OF_DAY ) {
              kseconds = 0;
          }
          // let system do it's own rollover
#if defined(__TURBOC__)||defined(__BORLANDC__)
          if ( (kseconds > 10 ) && (kseconds < LAST_UPDATES))
            *sysclock = (kseconds * TIME_X_TOP)/TIME_X_BOT;
#endif
      }
    }

    if (kinisr)
       return;

    if (k_user_int8 != 0)  /* user timer tick */
      (*k_user_int8)();

    if ( kpreemptive && kblocked == 0 &&
         kcurthread->th_inkernel == 0 ) {  /* !! where is this set? */
        rt_nextthread();
    }
}


// mimick settime()
void rt_settime( struct time *timep)
{
    kseconds = ((timep->ti_hour * 60)+ timep->ti_min) * 60L + timep->ti_sec;
    kirqscount = 0;
}

#if defined(__TURBOC__) || defined(__BORLANDC__)
// mimick stime
void rt_stime( time_t *t )
{
    struct tm *tm;
    struct time ti;

    dos_enter();
    tm = localtime( t );
    ti.ti_hour = tm->tm_hour;
    ti.ti_min  = tm->tm_min;
    ti.ti_sec  = tm->tm_sec;
    stime( t );
    rt_settime( &ti );
    dos_exit();
}

static void rt_settimer0( DWORD divisor )
{
    if ( divisor > 65535 ) divisor = 65535;
    disable();
    outportb( 0x43, 0x36 );             /* set mode */
    outportb( 0x40, divisor & 255 );    /* LSB */
    outportb( 0x40, divisor >> 8 );
    enable();
}
#endif

void rt_timerfreq( WORD persec )
{
#if defined(__TURBOC__) || defined(__BORLANDC__)
    rt_settimer0( 1193180 / persec );
#elif defined(__DJGPP__)
    struct itimerval tim;

    tim.it_interval.tv_usec = 1000000/ persec;
    tim.it_interval.tv_sec = 0;
    tim.it_value =tim.it_interval;
    setitimer( ITIMER_REAL, &tim, NULL );
#endif

    kirqsper = persec;
    kseconds = (*sysclock * TIME_X_BOT) / TIME_X_TOP;
    kirqscount = 0;

    if ( persec <= 1000 ) {
        kincr = 1000 / persec;
        kincr0 = 0;
    } else {
        kincr = 0;
        kincr0 = 10000 / persec;
    }
}

void rt_restoretimer( void )
{
#if defined(__TURBOC__) || defined(__BORLANDC__)
    rt_settimer0( 65535 );
    if ( koldtimer != NULL)
        setvect( 0x8, koldtimer );
#endif
}

#pragma exit rt_restoretimer 100
/*--------------------------------------------------------------------*/
int kctrlbrkhandler( void )
{
    /* ctrlbrk */
    if ( kctrlbreak )
        rt_halt("user pressed control break");
    return( 1 );    /* continue execution unhamperred */
}
/*--------------------------------------------------------------------*/

void rt_init( DWORD msgcount )
{
    thread_x *t;
    DWORD i;
    msg_x *m;

#ifdef USE_FP /* allow FP regs to be saved on stack */
    kcheckstacksig = 0;     /* SS:0 may have FP data */
#endif
    KFASTBLOCK();

    
#if defined(__TURBOC__) || defined(__BORLANDC__)
    /* handle Ctrl Break */
    ctrlbrk( kctrlbrkhandler );

    koldtimer = getvect( 0x8 );
    false_int.isr1 = ktimer;

    /* rt_restoretimer will restore this at shutdown */
    setvect( 0x8, (void interrupt (*)())&false_int );
#elif defined(__DJGPP__)
    {
        struct itimerval tim;

        tim.it_interval.tv_usec = 54945; /* 1000000/18.2 */
        tim.it_interval.tv_sec = 0;
        tim.it_value =tim.it_interval;
        signal( SIGALRM, (void(*)(int))ktimer );
        setitimer( ITIMER_REAL, &tim, NULL );
    }
#endif

    kmainthread = t = kcalloc( sizeof( thread_x ), 1 );
    kcurthread = t;
    t->th_sig = TH_SIG;
    t->th_ptrsig = TH_SIG;
    t->th_status = THS_THREAD;
    t->th_priority = 64;
    t->th_main = 1;
    t->th_name = "main";
#if defined(__TURBOC__) || defined(__BORLANDC__)
    t->th_stack = MK_FP( _SS, 0 );
    t->th_stacklen = 0xffff;    /* main thread has no limit */
    *(DWORD*)t->th_stack = STACK_SIG;
#elif defined(__DJGPP__)
    t->th_stack = 0;
    t->th_stacklen = 0xffffffff;
#endif
    kaddthread( &krun, t , TS_PRIORITY);

    /* allocate close to the number of messages we were asked for */
    i = kcorefree();
    if (i < (msgcount * sizeof( msg_x )))
        msgcount = (i /2) / sizeof( msg_x );
    kfreemessages = kcalloc( msgcount, sizeof( msg_x ));
    if ( kfreemessages != NULL ) {
        for ( i = 0 ; i < (msgcount-1) ; ++i ) {
            m = &kfreemessages[ i ];
            m->m_next = &kfreemessages[ i + 1 ];
        }
    }


    /* actual last one has NULL as m_next thanks to kcalloc */

    _dos_cs = cs_alloc();

    KFASTUNBLOCK();
}
/*--------------------------------------------------------------------*/

void rt_setpriority( thread_x *t, BYTE priority )
{
    thread_x **head;
    if ( t == NULL ) t = kcurthread;
    if ( priority == 0 ) priority = kcurthread->th_priority;

    if ( t->th_priority != priority ) {
        /* easiest way is to remove from current queue, then re-insert it */
        head = t->th_head;
        kremthread( t );
        t->th_priority = priority;
        kaddthread( head, t, TS_PRIORITY );
    }

    if ( t != kcurthread ) {
        if ( krun )
            if ( krun->th_priority < kcurthread->th_priority ) {
                /* another thread is more deserving than us */
                    if ( kblocked == 0 )
                        rt_yield();
            }
    }
}
/*--------------------------------------------------------------------*/

/*--------------------------------------------------------------------*/

void *rt_newthread( void (*ptr)(), DWORD arg, WORD stklength,
        BYTE priority, char *name )
{
    BYTE *stkptr;
//  WORD i;
    thread_x *t;

    stkptr = kcalloc( stklength, 1 );
    t = kcalloc( sizeof( thread_x ), 1 );
    if ( (t == NULL) || (stkptr == NULL))
        rt_halt("unable to allocate new thread");
    t->th_sig = TH_SIG;
    t->th_ptrsig = TH_SIG;
    t->th_parent = kcurthread;
    t->th_priority = priority ? priority : kcurthread->th_priority;
    t->th_status = THS_THREAD;
    t->th_fn = ptr;
    t->th_fnarg = arg;
    t->th_name = name;
    KFASTBLOCK();
#if defined(__TURBOC__)||defined(__BORLANDC__)
    if ( cpusetjmp( &t->th_ptr ) > 0 ) {
#elif defined(__DJGPP__)
    if ( setjmp( &t->th_ptr ) > 0 ) {
#endif
        /* in context of thread now */
        KFASTUNBLOCK();
        (*kcurthread->th_fn)(kcurthread->th_fnarg);

        if (kcurthread->th_end_value)
            ksendmessage( kcurthread->th_end_thread,
                          kcurthread->th_end_value,
                          kcurthread->th_end_data );
        kdestroythread( NULL );
//      rt_killcurthread();
    }

    /* The following allows the floating point emulator to work.  That
     * code assumes SS:0 to SS:415 can be used for register storage,
     * whereas kcalloc() returns Seg:04 and we use the next 4 bytes
     * for the stack signature.
     * So your choice is either to have the safety stack signature,
     * or support for FP emulation.
     * One still has to use a CS, because eRTOS does not save FP regs
     * at thread switch time.
     */
#ifdef USE_FP /* allow FP regs to be saved on stack */
#if defined(__TURBOC__) || defined(__BORLANDC__)
    t->th_ptr.j_ss = FP_SEG( stkptr ) + 1;
    t->th_ptr.j_sp = FP_OFF( stkptr ) + stklength - sizeof( &t->th_ptr) - 16;
#elif defined(__DJGPP__)
    t->th_ptr.__esp = (DWORD)(stkptr) + stklength - sizeof( &t->th_ptr) - 16;
#endif  // DJGPP
        
#else  /* default, offer a stack safety signature which may catch overflows*/
    *(DWORD*) stkptr = STACK_SIG ; /* R T O S */
#if defined(__TURBOC__) || defined(__BORLANDC__)
    t->th_ptr.j_ss = FP_SEG( stkptr );
    t->th_ptr.j_sp = FP_OFF( stkptr ) + stklength - sizeof( &t->th_ptr);
#endif // BORLANDC

#endif // USE_FP

    t->th_stack = stkptr;
    t->th_stacklen = stklength;
    kaddthread( &krun, t , TS_PRIORITY);
    KFASTUNBLOCK();
    return( t );
}

/*--------------------------------------------------------------------*/
int rt_stackused( thread_x *t)
{
    int len;
    char *p;
    if ( t == NULL ) t = kcurthread;

    if ( t == kmainthread) return( 0xffff );    /* cannot check stack of main thread */

    for ( len = sizeof( STACK_SIG ), p = t->th_stack + len ;
          (*p == 0) && (len < t->th_stacklen) ;
          len++, p++ );
    return( t->th_stacklen - len );
}

/*--------------------------------------------------------------------*/
static void add48( DWORD alo, WORD ahi, DWORD blo, DWORD *rlo, WORD *rhi )
#if defined( __TURBOC__ ) || defined( __BORLANDC__ )
{
    *rlo = blo + alo;
    asm jnc over
    ahi ++;
over:
    *rhi = ahi;
}
#elif defined( __DJGPP__ )
{
    *rlo = blo + alo;
    if ( *rlo < alo ) ahi++;
    *rhi = ahi;
}
#endif

void xrt_sleep( DWORD ms, char *file, int line )
{
    WORD flags;

   if ( kblocked > 0 ) {
      char buf[ 256 ];
      sprintf( buf, "sleep called when kblocked > 0, %s line %u\n", file, line );
      rt_halt( buf );
   }

    KFASTBLOCK();
    /* need to block so clock is not updating as we reference it */
    rt_cpu_block( &flags );
    {
//      WORD msg;
//      DWORD data;
        add48( ktime, ktime2, ms, &kcurthread->th_waketime, &kcurthread->th_waketime2);
        kcurthread->th_status |= THS_SLEEP;
        rt_cpu_unblock( &flags );
        KFASTUNBLOCK();
//      kreadmessage( &msg, &data );
        kreadspecialmessage( EMSG_TIMER );
    }
}

void kwindow( int left, int top, int right, int bottom )
{
    kusewindows = 1;
    KFASTBLOCK();
    kcurthread->th_left = left+1;
    kcurthread->th_right = right-1;
    kcurthread->th_top = top+1;
    kcurthread->th_bottom = bottom-1;
    window( left, top, right, bottom );
    textbackground( WHITE );
    clrscr();
    window( left+1, top+1, right-1, bottom-1 );
    textbackground( BLACK );
    clrscr();
    KFASTUNBLOCK();
}

/*--------------------------------------------------------------------*/

#if defined( __BORLANDC__ ) || defined( __TURBOC__ )
//void interrupt *rt_enableirq( int irq, void interrupt (*isr)() )
void interrupt (far *rt_enableirq (int irq, void interrupt (*isr)()))()
{
    void interrupt (*oldisr)() = NULL;

    if ( irq < 8 ) {
        oldisr = getvect( 8 + irq );
        setvect( 8 + irq, isr );
        /* turn on 8259 PIC */
        outportb( OCW1, inportb( OCW1 ) & ~ (1 << irq ));
    } else {
        irq -= 8;
        oldisr = getvect( 0x70 + irq );
        setvect( 0x70 + irq, isr );
        /* turn on 8259 PIC */
        outportb( OCW1_2, inport( OCW1_2 ) & ~ ( 1 << irq ));
    }
    return( oldisr );
}
/*--------------------------------------------------------------------*/
void rt_disableirq( int irq, void interrupt (*oldisr)() )
{
    if ( irq < 8 ) {
        /* turn off 8259 PIC */
        outportb( OCW1 , inportb( OCW1) | (1 << irq ));
        setvect( irq + 8, oldisr );
    } else {
        irq -= 8;
        /* turn off 8259 PIC2 */
        outportb( OCW1_2, inportb( OCW1_2) | (1 << irq ));
        setvect( irq + 0x70, oldisr );
    }
}

#elif defined(__DJGPP__)

static struct irq_handler_info isr[16];

irq_handler_info *rt_enableirq (int irq, void (*irq_handler)())
{
  struct irq_handler_info *inf = isr + irq;
  BYTE   intr = (irq < 8) ? (irq + 8) : (irq + 0x70-8);

  if (irq < 0 || irq >= DIM(isr) || inf->new_handler.pm_offset)
     return (NULL);

  if (_go32_dpmi_get_protected_mode_interrupt_vector (intr,&inf->old_handler))
     return (NULL);

  inf->new_handler.pm_offset = (unsigned long) irq_handler;
  if (_go32_dpmi_allocate_iret_wrapper (&inf->new_handler))
  {
    inf->new_handler.pm_offset = 0;
    return (NULL);
  }

  _go32_dpmi_set_protected_mode_interrupt_vector (intr,&inf->new_handler);

  if ( irq < 8 )
  {
    /* turn on 8259 PIC */
    outportb (OCW1, inportb(OCW1) & ~(1 << irq));
  }
  else
  {
    irq -= 8;
    /* turn on 8259 PIC */
    outportb (OCW1_2, inportb(OCW1_2) & ~(1 << irq));
  }
  return (inf);
}

void rt_disableirq (int irq, irq_handler_info *inf)
{
  BYTE intr = (irq < 8) ? (irq + 8) : (irq + 0x70-8);

  if (irq < 0 || irq >= DIM(isr) || !inf->new_handler.pm_offset)
     return;

  if (irq < 8)
  {
    /* turn off 8259 PIC */
    outportb (OCW1 , inportb(OCW1) | (1 << irq));
  }
  else
  {
    irq -= 8;
    /* turn off 8259 PIC2 */
    outportb( OCW1_2, inportb(OCW1_2) | (1 << irq));
  }

  _go32_dpmi_set_protected_mode_interrupt_vector (intr, &inf->old_handler);
  _go32_dpmi_free_iret_wrapper (&inf->new_handler);
  inf->new_handler.pm_offset = isr[irq].new_handler.pm_offset = 0;
}
#endif // TURBOC BORLANDC

/*--------------------------------------------------------------------*/
void rt_eoi( int irq )
{
    if ( irq < 8 )
        outportb( OCW2, 0x20 );
    else
        outportb( OCW2_2, 0x20 );
}

/*--------------------------------------------------------------------*/
char *kstrdup( const char *s )
{
    char *p;

    if ( s == NULL )
        return( NULL );

    if ( ( p = kcalloc( strlen( s ) + 1, 1 )) != NULL )
        strcpy( p, s );
    return( p );
}
