/*************************************************************************** * Copyright (c) 2024 Microsoft Corporation * * 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 **************************************************************************/ /**************************************************************************/ /**************************************************************************/ /** */ /** ThreadX Component */ /** */ /** Initialize */ /** */ /**************************************************************************/ /**************************************************************************/ #define TX_SOURCE_CODE /* Include necessary system files. */ #include "tx_api.h" #include #include #include #include #include #include /* Define various Linux objects used by the ThreadX port. */ pthread_mutex_t _tx_linux_mutex; sem_t _tx_linux_semaphore; sem_t _tx_linux_semaphore_no_idle; ULONG _tx_linux_global_int_disabled_flag; struct timespec _tx_linux_time_stamp; __thread int _tx_linux_threadx_thread = 0; /* Define signals for linux thread. */ #define SUSPEND_SIG SIGUSR1 #define RESUME_SIG SIGUSR2 static sigset_t _tx_linux_thread_wait_mask; static __thread int _tx_linux_thread_suspended; static sem_t _tx_linux_thread_timer_wait; static sem_t _tx_linux_thread_other_wait; /* Define simulated timer interrupt. This is done inside a thread, which is how other interrupts may be defined as well. See code below for an example. */ pthread_t _tx_linux_timer_id; sem_t _tx_linux_timer_semaphore; sem_t _tx_linux_isr_semaphore; void *_tx_linux_timer_interrupt(void *p); void _tx_linux_thread_resume_handler(int sig); void _tx_linux_thread_suspend_handler(int sig); void _tx_linux_thread_suspend(pthread_t thread_id); #ifdef TX_LINUX_DEBUG_ENABLE extern ULONG _tx_thread_system_state; extern UINT _tx_thread_preempt_disable; extern TX_THREAD *_tx_thread_current_ptr; extern TX_THREAD *_tx_thread_execute_ptr; /* Define debug log in order to debug Linux issues with this port. */ typedef struct TX_LINUX_DEBUG_ENTRY_STRUCT { char *tx_linux_debug_entry_action; struct timespec tx_linux_debug_entry_timestamp; char *tx_linux_debug_entry_file; unsigned long tx_linux_debug_entry_line; pthread_mutex_t tx_linux_debug_entry_mutex; unsigned long tx_linux_debug_entry_int_disabled_flag; ULONG tx_linux_debug_entry_system_state; UINT tx_linux_debug_entry_preempt_disable; TX_THREAD *tx_linux_debug_entry_current_thread; TX_THREAD *tx_linux_debug_entry_execute_thread; } TX_LINUX_DEBUG_ENTRY; /* Define the maximum size of the Linux debug array. */ #ifndef TX_LINUX_DEBUG_EVENT_SIZE #define TX_LINUX_DEBUG_EVENT_SIZE 400 #endif /* Define the circular array of Linux debug entries. */ TX_LINUX_DEBUG_ENTRY _tx_linux_debug_entry_array[TX_LINUX_DEBUG_EVENT_SIZE]; /* Define the Linux debug index. */ unsigned long _tx_linux_debug_entry_index = 0; /* Now define the debug entry function. */ void _tx_linux_debug_entry_insert(char *action, char *file, unsigned long line) { pthread_mutex_t temp_copy; /* Save the current critical section value. */ temp_copy = _tx_linux_mutex; /* Lock mutex. */ tx_linux_mutex_lock(_tx_linux_mutex); /* Get the time stamp. */ clock_gettime(CLOCK_REALTIME, &_tx_linux_time_stamp); /* Setup the debub entry. */ _tx_linux_debug_entry_array[_tx_linux_debug_entry_index].tx_linux_debug_entry_action = action; _tx_linux_debug_entry_array[_tx_linux_debug_entry_index].tx_linux_debug_entry_timestamp = _tx_linux_time_stamp; _tx_linux_debug_entry_array[_tx_linux_debug_entry_index].tx_linux_debug_entry_file = file; _tx_linux_debug_entry_array[_tx_linux_debug_entry_index].tx_linux_debug_entry_line = line; _tx_linux_debug_entry_array[_tx_linux_debug_entry_index].tx_linux_debug_entry_mutex = temp_copy; _tx_linux_debug_entry_array[_tx_linux_debug_entry_index].tx_linux_debug_entry_int_disabled_flag = _tx_linux_global_int_disabled_flag; _tx_linux_debug_entry_array[_tx_linux_debug_entry_index].tx_linux_debug_entry_system_state = _tx_thread_system_state; _tx_linux_debug_entry_array[_tx_linux_debug_entry_index].tx_linux_debug_entry_preempt_disable = _tx_thread_preempt_disable; _tx_linux_debug_entry_array[_tx_linux_debug_entry_index].tx_linux_debug_entry_current_thread = _tx_thread_current_ptr; _tx_linux_debug_entry_array[_tx_linux_debug_entry_index].tx_linux_debug_entry_execute_thread = _tx_thread_execute_ptr; /* Now move to the next entry. */ _tx_linux_debug_entry_index++; /* Determine if we need to wrap the list. */ if (_tx_linux_debug_entry_index >= TX_LINUX_DEBUG_EVENT_SIZE) { /* Yes, wrap the list! */ _tx_linux_debug_entry_index = 0; } /* Unlock mutex. */ tx_linux_mutex_unlock(_tx_linux_mutex); } #endif /* Define the ThreadX timer interrupt handler. */ void _tx_timer_interrupt(void); /* Define other external function references. */ VOID _tx_initialize_low_level(VOID); VOID _tx_thread_context_save(VOID); VOID _tx_thread_context_restore(VOID); /* Define other external variable references. */ extern VOID *_tx_initialize_unused_memory; /**************************************************************************/ /* */ /* FUNCTION RELEASE */ /* */ /* _tx_initialize_low_level Linux/GNU */ /* 6.1 */ /* AUTHOR */ /* */ /* William E. Lamie, Microsoft Corporation */ /* */ /* DESCRIPTION */ /* */ /* This function is responsible for any low-level processor */ /* initialization, including setting up interrupt vectors, setting */ /* up a periodic timer interrupt source, saving the system stack */ /* pointer for use in ISR processing later, and finding the first */ /* available RAM memory address for tx_application_define. */ /* */ /* INPUT */ /* */ /* None */ /* */ /* OUTPUT */ /* */ /* None */ /* */ /* CALLS */ /* */ /* sched_setaffinity */ /* getpid */ /* _tx_linux_thread_init */ /* pthread_setschedparam */ /* pthread_mutexattr_init */ /* pthread_mutex_init */ /* _tx_linux_thread_suspend */ /* sem_init */ /* pthread_create */ /* printf */ /* */ /* CALLED BY */ /* */ /* _tx_initialize_kernel_enter ThreadX entry function */ /* */ /* RELEASE HISTORY */ /* */ /* DATE NAME DESCRIPTION */ /* */ /* 09-30-2020 William E. Lamie Initial Version 6.1 */ /* */ /**************************************************************************/ VOID _tx_initialize_low_level(VOID) { struct sched_param sp; pthread_mutexattr_t attr; #ifdef TX_LINUX_MULTI_CORE cpu_set_t mask; sched_getaffinity(getpid(), sizeof(mask), &mask); if (CPU_COUNT(&mask) > 1) { srand((ULONG)pthread_self()); /* Limit this ThreadX simulation on Linux to a single core. */ CPU_ZERO(&mask); CPU_SET(rand() % get_nprocs(), &mask); if (sched_setaffinity(getpid(), sizeof(mask), &mask) != 0) { /* Error restricting the process to one core. */ printf("ThreadX Linux error restricting the process to one core!\n"); while(1) { } } } #endif /* Pickup the first available memory address. */ /* Save the first available memory address. */ _tx_initialize_unused_memory = malloc(TX_LINUX_MEMORY_SIZE); /* Init Linux thread. */ _tx_linux_thread_init(); /* Set priority and schedual of main thread. */ sp.sched_priority = TX_LINUX_PRIORITY_SCHEDULE; pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp); /* Create the system critical section. This is used by the scheduler thread (which is the main thread) to block all other stuff out. */ pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&_tx_linux_mutex, &attr); sem_init(&_tx_linux_semaphore, 0, 0); #ifdef TX_LINUX_NO_IDLE_ENABLE sem_init(&_tx_linux_semaphore_no_idle, 0, 0); #endif /* TX_LINUX_NO_IDLE_ENABLE */ /* Initialize the global interrupt disabled flag. */ _tx_linux_global_int_disabled_flag = TX_FALSE; /* Create semaphore for timer thread. */ sem_init(&_tx_linux_timer_semaphore, 0, 0); /* Create semaphore for ISR thread. */ sem_init(&_tx_linux_isr_semaphore, 0, 0); /* Setup periodic timer interrupt. */ if(pthread_create(&_tx_linux_timer_id, NULL, _tx_linux_timer_interrupt, NULL)) { /* Error creating the timer interrupt. */ printf("ThreadX Linux error creating timer interrupt thread!\n"); while(1) { } } /* Otherwise, we have a good thread create. Now set the priority to a level lower than the system thread but higher than the application threads. */ sp.sched_priority = TX_LINUX_PRIORITY_ISR; pthread_setschedparam(_tx_linux_timer_id, SCHED_FIFO, &sp); /* Done, return to caller. */ } /* This routine is called after initialization is complete in order to start all interrupt threads. Interrupt threads in addition to the timer may be added to this routine as well. */ void _tx_initialize_start_interrupts(void) { /* Kick the timer thread off to generate the ThreadX periodic interrupt source. */ tx_linux_sem_post(&_tx_linux_timer_semaphore); } /* Define the ThreadX system timer interrupt. Other interrupts may be simulated in a similar way. */ void *_tx_linux_timer_interrupt(void *p) { struct timespec ts; long timer_periodic_nsec; int err; (VOID)p; /* Calculate periodic timer. */ timer_periodic_nsec = 1000000000 / TX_TIMER_TICKS_PER_SECOND; nice(10); /* Wait startup semaphore. */ tx_linux_sem_wait(&_tx_linux_timer_semaphore); while(1) { clock_gettime(CLOCK_REALTIME, &ts); ts.tv_nsec += timer_periodic_nsec; if (ts.tv_nsec > 1000000000) { ts.tv_nsec -= 1000000000; ts.tv_sec++; } do { if (sem_timedwait(&_tx_linux_timer_semaphore, &ts) == 0) { break; } err = errno; } while (err != ETIMEDOUT); /* Call ThreadX context save for interrupt preparation. */ _tx_thread_context_save(); /* Call trace ISR enter event insert. */ _tx_trace_isr_enter_insert(0); /* Call the ThreadX system timer interrupt processing. */ _tx_timer_interrupt(); /* Call trace ISR exit event insert. */ _tx_trace_isr_exit_insert(0); /* Call ThreadX context restore for interrupt completion. */ _tx_thread_context_restore(); #ifdef TX_LINUX_NO_IDLE_ENABLE tx_linux_mutex_lock(_tx_linux_mutex); /* Make sure semaphore is 0. */ while(!sem_trywait(&_tx_linux_semaphore_no_idle)); /* Wakeup the system thread by setting the system semaphore. */ tx_linux_sem_post(&_tx_linux_semaphore_no_idle); tx_linux_mutex_unlock(_tx_linux_mutex); #endif /* TX_LINUX_NO_IDLE_ENABLE */ } } /* Define functions for linux thread. */ void _tx_linux_thread_resume_handler(int sig) { (VOID)sig; } void _tx_linux_thread_suspend_handler(int sig) { (VOID)sig; if(pthread_equal(pthread_self(), _tx_linux_timer_id)) tx_linux_sem_post_nolock(&_tx_linux_thread_timer_wait); else tx_linux_sem_post_nolock(&_tx_linux_thread_other_wait); if(_tx_linux_thread_suspended) return; _tx_linux_thread_suspended = 1; sigsuspend(&_tx_linux_thread_wait_mask); _tx_linux_thread_suspended = 0; } void _tx_linux_thread_suspend(pthread_t thread_id) { /* Send signal. */ tx_linux_mutex_lock(_tx_linux_mutex); pthread_kill(thread_id, SUSPEND_SIG); tx_linux_mutex_unlock(_tx_linux_mutex); /* Wait until signal is received. */ if(pthread_equal(thread_id, _tx_linux_timer_id)) tx_linux_sem_wait(&_tx_linux_thread_timer_wait); else tx_linux_sem_wait(&_tx_linux_thread_other_wait); } void _tx_linux_thread_resume(pthread_t thread_id) { /* Send signal. */ tx_linux_mutex_lock(_tx_linux_mutex); pthread_kill(thread_id, RESUME_SIG); tx_linux_mutex_unlock(_tx_linux_mutex); } void _tx_linux_thread_init() { struct sigaction sa; /* Create semaphore for linux thread. */ sem_init(&_tx_linux_thread_timer_wait, 0, 0); sem_init(&_tx_linux_thread_other_wait, 0, 0); sigfillset(&_tx_linux_thread_wait_mask); sigdelset(&_tx_linux_thread_wait_mask, RESUME_SIG); sigfillset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = _tx_linux_thread_resume_handler; sigaction(RESUME_SIG, &sa, NULL); sa.sa_handler = _tx_linux_thread_suspend_handler; sigaction(SUSPEND_SIG, &sa, NULL); }