/***************************************************************************/
/* Copyright (c) 2024 Microsoft Corporation                                */
/* Copyright (c) 2026 Eclipse ThreadX contributors                         */
/*                                                                         */
/* This program and the accompanying materials are made available under    */
/* the terms of the MIT License which is available at                      */
/* https://opensource.org/licenses/MIT.                                    */
/*                                                                         */
/* SPDX-License-Identifier: MIT                                            */
/***************************************************************************/

/* This test checks out the delayed suspension clear from tx_thread_resume.  */

#include <stdio.h>
#include "tx_api.h"
#include "tx_thread.h"
#include "tx_timer.h"


#define DEMO_STACK_SIZE TEST_STACK_SIZE_PRINTF


/* Define the ThreadX object control blocks...  */

static TX_THREAD               thread_0;
static TX_THREAD               thread_1;
static TX_SEMAPHORE            semaphore_0;


/* Define thread prototypes.  */

static void    thread_0_entry(ULONG thread_input);
static void    thread_1_entry(ULONG thread_input);


/* Prototype for test control return.  */
void  test_control_return(UINT status);


/* Define the ISR dispatch routine.  */

#ifndef TX_NOT_INTERRUPTABLE

#if defined(TX_WIN32_MEMORY_SIZE) || defined(TX_LINUX_MEMORY_SIZE)
/* Use larger array size when running on Win32 test platform because of greater speed.  */
#define ARRAY_SIZE              100
#else
#define ARRAY_SIZE              10
#endif


/* Define the ISR dispatch.  */

extern VOID             (*test_isr_dispatch)(void);
static void             thread_2_entry(ULONG thread_input);

static UINT             delayed_suspend_set;

static ULONG            thread_2_counter;
static ULONG            thread_2_counter_capture;

static ULONG            min_loop_count;
static ULONG            max_loop_count;
static ULONG            loop_count;
static volatile ULONG   count;
static volatile ULONG   destination =  0;
static ULONG            start_time;
static ULONG            lower_bound;
static ULONG            upper_bound;
static ULONG            current_iterations;
#ifdef DEBUG_1
static ULONG            last_loop_count;
#endif
static TX_THREAD        thread_2;
static TX_SEMAPHORE     semaphore_1;

static ULONG            array_delay[ARRAY_SIZE];

static ULONG   delay_function(void)
{

ULONG   accumulator;
ULONG   i;

    for (i = 0; i < ARRAY_SIZE; i++)
        array_delay[i] = i;

    for (i = 0; i < ARRAY_SIZE-4; i++)
    {
        array_delay[i] =  (array_delay[i+1] * array_delay[i+2]) * (array_delay[i+3] * array_delay[i+4]);
    }

    accumulator =  0;
    for (i = 0; i < ARRAY_SIZE; i++)
        accumulator =  accumulator + array_delay[i];

    return(accumulator);
}


static void    test_isr(void)
{
ULONG i;

    /* Determine if we are in calibration mode.  */
    if (loop_count != 0xFFFFFFFF)
    {
        if (loop_count < min_loop_count)
            min_loop_count =  loop_count;
        if (loop_count > max_loop_count)
            max_loop_count =  loop_count;

        lower_bound = loop_count - 1;
        upper_bound = loop_count + 1;
        if (lower_bound < min_loop_count)
            lower_bound =  min_loop_count;
        if (upper_bound > max_loop_count)
            lower_bound = max_loop_count;

        if ((current_iterations < lower_bound) || (current_iterations > upper_bound))
            current_iterations =  lower_bound;
        
#ifdef DEBUG_1
        /* Last loop count.  */
    last_loop_count =  loop_count;
#endif

        /* Reset the loop count to all ones!  */
        loop_count =  0xFFFFFFFF;
    }
    count++;
    for (i = 0; i < (count%32); i++)
        destination++;

    /* Check to see if the interrupt occurred in the middle of the suspension.  */
    if ((thread_2.tx_thread_suspending) && (delayed_suspend_set == 0))
    {

        /* Yes, we have taken the interrupt in the middle of a thread suspension.  */

        /* Indicate we have got the condition.  */
        delayed_suspend_set =  1;

        /* Capture the current thread 2 counter.  */
        thread_2_counter_capture =  thread_2_counter;

        /* Now attempt to set the delayed suspension.  */
        tx_thread_suspend(&thread_2);

        /* Check for the delayed suspension flag being set.  */
        if (thread_2.tx_thread_delayed_suspend != 1)
        {

            /* Error!  Setup the counters to indicate an error.  */
            thread_2_counter =  0xEEEEEEEE;
            thread_2_counter_capture =  0xFFFFFFFF;
        }

        /* Now, abort the suspension for thread 2... the thread should switch to a pure suspended state.  */
        tx_thread_wait_abort(&thread_2);

        /* Check for the proper state.  */
        if (thread_2.tx_thread_state != TX_SUSPENDED)
        {

            /* Error!  Setup the counters to indicate an error.  */
            thread_2_counter =  0xEEEEEEEE;
            thread_2_counter_capture =  0xFFFFFFFF;
        }
    }

}
#endif



/* Define what the initial system looks like.  */

#ifdef CTEST
void test_application_define(void *first_unused_memory)
#else
void    threadx_thread_delayed_suspension_application_define(void *first_unused_memory)
#endif
{

CHAR    *pointer;

    /* Put first available memory address into a character pointer.  */
    pointer =  (CHAR *) first_unused_memory;

    /* Put system definition stuff in here, e.g. thread creates and other assorted
       create information.  */

    /* Create the main thread.  */
    tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
            pointer, DEMO_STACK_SIZE,
            2, 2, TX_NO_TIME_SLICE, TX_AUTO_START);
    pointer = pointer + DEMO_STACK_SIZE;

    /* Create threads 1 and 2.  */
    tx_thread_create(&thread_1, "thread 1", thread_1_entry, 1,
            pointer, DEMO_STACK_SIZE,
            2, 2, TX_NO_TIME_SLICE, TX_AUTO_START);
    pointer = pointer + DEMO_STACK_SIZE;

    /* Create the semaphore.  */
    tx_semaphore_create(&semaphore_0, "semaphore 0", 0);

#ifndef TX_NOT_INTERRUPTABLE

    tx_thread_create(&thread_2, "thread 2", thread_2_entry, 2,
            pointer, DEMO_STACK_SIZE,
            1, 1, TX_NO_TIME_SLICE, TX_DONT_START);
    pointer = pointer + DEMO_STACK_SIZE;

    tx_semaphore_create(&semaphore_1, "semaphore 1", 0);

    thread_2_counter =  0;
    thread_2_counter_capture =  0;
    min_loop_count =  0xFFFFFFFF;
    max_loop_count =  0;
    loop_count =      0xFFFFFFFF;
#ifdef DEBUG_1
    last_loop_count = 0;
#endif
#endif
}



/* Define the test threads.  */

static void    thread_0_entry(ULONG thread_input)
{

UINT    status;


    /* Inform user.  */
    printf("Running Thread Delayed Suspension Clearing Test..................... ");
    /* Relinquish to the other thread.  */
    tx_thread_relinquish();

    /* At this point thread 1 has suspended on the semaphore.  */

    /* Suspend the already suspended thread.  */
    tx_thread_suspend(&thread_1);

    /* Set the semaphore, which should make it go into a suspend state.  */
    tx_semaphore_put(&semaphore_0);

    /* Resume the other thread so it runs again.  */
    tx_thread_resume(&thread_1);

    /* Relinquish so it can run again.  */
    tx_thread_relinquish();

    /* Suspend the already suspended thread.  */
    tx_thread_suspend(&thread_1);

    /* Now, clear the delayed suspension.  */
    status = tx_thread_resume(&thread_1);

    if (status != TX_SUSPEND_LIFTED)
    {

        /* Delayed suspension error.  */
        printf("ERROR #1\n");
        test_control_return(1);
    }

#ifndef TX_NOT_INTERRUPTABLE

    /* Setup the test ISR.  */
    test_isr_dispatch =  test_isr;

    /* Resume the test thread. */
    tx_thread_resume(&thread_2);

    /* Wait until we see the delayed suspension set flag.  */
    while(delayed_suspend_set == 0)
    {
        /* Abort the suspension for thread 2.  */
        tx_thread_wait_abort(&thread_2);

        /* Just relinquish.  */
        tx_thread_relinquish();
    }

    /* Relinquish one more time to make sure thread 2 could run if it is ready.  */
    tx_thread_relinquish();

    /* At this point, check for an error.  */
    if (thread_2_counter != thread_2_counter_capture)
    {

        /* Delayed suspension error... thread kept running!  */
        printf("ERROR #2\n");
        test_control_return(1);
    }
#endif

    /* Successful test.  */
    printf("SUCCESS!\n");
    test_control_return(0);
}


static void    thread_1_entry(ULONG thread_input)
{

UINT    status;

    /* This thread simply gets the semaphore...  */
    while(1)
    {

        /* Get semaphore.  */
        status =  tx_semaphore_get(&semaphore_0, TX_WAIT_FOREVER);

        /* Check completion status.  */
        if (status != TX_SUCCESS)
            break;
    }
}


#ifndef TX_NOT_INTERRUPTABLE


static void    thread_2_entry(ULONG thread_input)
{

ULONG   i;

    /* Callibrate the loop count from thread sleep.  */
    for (i = 0; i < 10; i++)
    {

        /* Sleep to get a fresh time.  */
        tx_thread_sleep(1);

        start_time =  _tx_timer_system_clock;
        do
        {

            /* Call delay function.  */
            delay_function();
            loop_count++;
        } while (start_time == _tx_timer_system_clock);

        /* Wait to reset the loop count.  */
        tx_thread_sleep(1);
    }

    /* Setup the lower and upper bounds.  */
    lower_bound =  min_loop_count;
    if (lower_bound > 5)
        lower_bound = lower_bound - 5;
    upper_bound =  max_loop_count + 5;

    current_iterations =  lower_bound;

    /* This thread simply suspends over and over...  */
    while(1)
    {

        /* Sleep to get a fresh starting time.  */
        tx_thread_sleep(1);

        loop_count =  0;
        start_time =  _tx_timer_system_clock;
        do
        {
            /* Call delay function.  */
            delay_function();
            loop_count++;
        } while (loop_count < current_iterations);      

        /* Suspend this thread.  */
        tx_semaphore_get(&semaphore_1, TX_WAIT_FOREVER);

        /* Adjust the current iterations.  */
        current_iterations++;
        if (current_iterations > upper_bound)
        {
            if (lower_bound > min_loop_count)
                lower_bound--;
            if (upper_bound < max_loop_count)
                upper_bound++;
            current_iterations =  lower_bound;
        }

        /* Increment the thread counter.  */
        thread_2_counter++;
    }
}

#endif
