/*
    ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010 Giovanni Di Sirio.
    This file is part of ChibiOS/RT.
    ChibiOS/RT is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    ChibiOS/RT is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see .
*/
/**
 * @file    chthreads.c
 * @brief   Threads code.
 *
 * @addtogroup threads
 * @details Threads related APIs and services.
 *
 *          
Operation mode
 *          A thread is an abstraction of an independent instructions flow.
 *          In ChibiOS/RT a thread is represented by a "C" function owning
 *          a processor context, state informations and a dedicated stack
 *          area. In this scenario static variables are shared among all
 *          threads while automatic variables are local to the thread.
 *          Operations defined for threads:
 *          - Init, a thread is prepared and put in the suspended
 *            state.
 *          - Create, a thread is started on the specified thread
 *            function. This operation is available in multiple variants,
 *            both static and dynamic.
 *          - Exit, a thread terminates by returning from its top
 *            level function or invoking a specific API, the thread can
 *            return a value that can be retrieved by other threads.
 *          - Wait, a thread waits for the termination of another
 *            thread and retrieves its return value.
 *          - Resume, a thread created in suspended state is started.
 *          - Sleep, the execution of a thread is suspended for the
 *            specified amount of time or the specified future absolute time
 *            is reached.
 *          - SetPriority, a thread changes its own priority level.
 *          - Yield, a thread voluntarily renounces to its time slot.
 *          .
 *          The threads subsystem is implicitly included in kernel however
 *          some of its part may be excluded by disabling them in @p chconf.h,
 *          see the @p CH_USE_WAITEXIT and @p CH_USE_DYNAMIC configuration
 *          options.
 * @{
 */
#include "ch.h"
/**
 * @brief   Initializes a thread structure.
 *
 * @param[in] tp        pointer to the thread
 * @param[in] prio      the priority level for the new thread
 * @return              The same thread pointer passed as parameter.
 */
Thread *init_thread(Thread *tp, tprio_t prio) {
  tp->p_flags = THD_MEM_MODE_STATIC;
  tp->p_prio = prio;
  tp->p_state = THD_STATE_SUSPENDED;
#if CH_USE_REGISTRY
  REG_INSERT(tp);
#endif
#if CH_USE_DYNAMIC
  tp->p_refs = 1;
#endif
#if CH_USE_NESTED_LOCKS
  tp->p_locks = 0;
#endif
#if CH_DBG_THREADS_PROFILING
  tp->p_time = 0;
#endif
#if CH_USE_MUTEXES
  tp->p_realprio = prio;
  tp->p_mtxlist = NULL;
#endif
#if CH_USE_WAITEXIT
  list_init(&tp->p_waiting);
#endif
#if CH_USE_MESSAGES
  queue_init(&tp->p_msgqueue);
#endif
#if CH_USE_EVENTS
  tp->p_epending = 0;
#endif
  THREAD_EXT_INIT(tp);
  return tp;
}
#if CH_DBG_FILL_THREADS
static void memfill(uint8_t *startp, uint8_t *endp, uint8_t v) {
  while (startp < endp)
    *startp++ = v;
}
#endif
/**
 * @brief   Initializes a new thread.
 * @details The new thread is initialized but not inserted in the ready list,
 *          the initial state is @p THD_STATE_SUSPENDED.
 * @note    A thread can terminate by calling @p chThdExit() or by simply
 *          returning from its main function.
 * @note    This function can be invoked from within an interrupt handler
 *          even if it is not an I-Class API because it does not touch
 *          any critical kernel data structure.
 *
 * @param[out] wsp      pointer to a working area dedicated to the thread stack
 * @param[in] size      size of the working area
 * @param[in] prio      the priority level for the new thread
 * @param[in] pf        the thread function
 * @param[in] arg       an argument passed to the thread function. It can be
 *                      @p NULL.
 * @return              The pointer to the @p Thread structure allocated for
 *                      the thread into the working space area.
 */
Thread *chThdInit(void *wsp, size_t size, tprio_t prio, tfunc_t pf, void *arg) {
  /* Thread structure is layed out in the lower part of the thread workspace */
  Thread *tp = wsp;
  chDbgCheck((wsp != NULL) && (size >= THD_WA_SIZE(0)) &&
             (prio <= HIGHPRIO) && (pf != NULL),
             "chThdInit");
#if CH_DBG_FILL_THREADS
  memfill((uint8_t *)wsp, (uint8_t *)wsp + sizeof(Thread), THREAD_FILL_VALUE);
  memfill((uint8_t *)wsp + sizeof(Thread),
          (uint8_t *)wsp + size, STACK_FILL_VALUE);
#endif
  SETUP_CONTEXT(wsp, size, pf, arg);
  return init_thread(tp, prio);
}
/**
 * @brief   Creates a new thread into a static memory area.
 * @note    A thread can terminate by calling @p chThdExit() or by simply
 *          returning from its main function.
 *
 * @param[out] wsp      pointer to a working area dedicated to the thread stack
 * @param[in] size      size of the working area
 * @param[in] prio      the priority level for the new thread
 * @param[in] pf        the thread function
 * @param[in] arg       an argument passed to the thread function. It can be
 *                      @p NULL.
 * @return              The pointer to the @p Thread structure allocated for
 *                      the thread into the working space area.
 */
Thread *chThdCreateStatic(void *wsp, size_t size,
                          tprio_t prio, tfunc_t pf, void *arg) {
  return chThdResume(chThdInit(wsp, size, prio, pf, arg));
}
#if CH_USE_DYNAMIC && CH_USE_HEAP
/**
 * @brief   Creates a new thread allocating the memory from the heap.
 * @note    A thread can terminate by calling @p chThdExit() or by simply
 *          returning from its main function.
 * @note    The memory allocated for the thread is not released when the thread
 *          terminates but when a @p chThdWait() is performed.
 * @note    The function is available only if the @p CH_USE_DYNAMIC,
 *          @p CH_USE_HEAP and @p CH_USE_WAITEXIT options are enabled
 *          in @p chconf.h.
 *
 * @param[in] heapp     heap from which allocate the memory or @p NULL for the
 *                      default heap
 * @param[in] size      size of the working area to be allocated
 * @param[in] prio      the priority level for the new thread
 * @param[in] pf        the thread function
 * @param[in] arg       an argument passed to the thread function. It can be
 *                      @p NULL.
 * @return              The pointer to the @p Thread structure allocated for
 *                      the thread into the working space area.
 * @retval NULL         if the memory cannot be allocated.
 */
Thread *chThdCreateFromHeap(MemoryHeap *heapp, size_t size,
                            tprio_t prio, tfunc_t pf, void *arg) {
  void *wsp;
  Thread *tp;
  wsp = chHeapAlloc(heapp, size);
  if (wsp == NULL)
    return NULL;
  tp = chThdInit(wsp, size, prio, pf, arg);
  tp->p_flags = THD_MEM_MODE_HEAP;
  return chThdResume(tp);
}
#endif /* CH_USE_DYNAMIC && CH_USE_HEAP */
#if CH_USE_DYNAMIC && CH_USE_MEMPOOLS
/**
 * @brief   Creates a new thread allocating the memory from the specified
 *          Memory Pool.
 * @note    A thread can terminate by calling @p chThdExit() or by simply
 *          returning from its main function.
 * @note    The memory allocated for the thread is not released when the thread
 *          terminates but when a @p chThdWait() is performed.
 * @note    The function is available only if the @p CH_USE_DYNAMIC,
 *          @p CH_USE_MEMPOOLS and @p CH_USE_WAITEXIT options are enabled
 *          in @p chconf.h.
 *
 * @param[in] mp        pointer to the memory pool object
 * @param[in] prio      the priority level for the new thread
 * @param[in] pf        the thread function
 * @param[in] arg       an argument passed to the thread function. It can be
 *                      @p NULL.
 * @return              The pointer to the @p Thread structure allocated for
 *                      the thread into the working space area.
 * @retval  NULL        if the memory pool is empty.
 */
Thread *chThdCreateFromMemoryPool(MemoryPool *mp, tprio_t prio,
                                  tfunc_t pf, void *arg) {
  void *wsp;
  Thread *tp;
  chDbgCheck(mp != NULL, "chThdCreateFromMemoryPool");
  wsp = chPoolAlloc(mp);
  if (wsp == NULL)
    return NULL;
  tp = chThdInit(wsp, mp->mp_object_size, prio, pf, arg);
  tp->p_flags = THD_MEM_MODE_MEMPOOL;
  tp->p_mpool = mp;
  return chThdResume(tp);
}
#endif /* CH_USE_DYNAMIC && CH_USE_MEMPOOLS */
/**
 * @brief   Changes the running thread priority level then reschedules if
 *          necessary.
 * @note    The function returns the real thread priority regardless of the
 *          current priority that could be higher than the real priority
 *          because the priority inheritance mechanism.
 *
 * @param[in] newprio   the new priority level of the running thread
 * @return              The old priority level.
 */
tprio_t chThdSetPriority(tprio_t newprio) {
  tprio_t oldprio;
  chDbgCheck((newprio >= LOWPRIO) && (newprio <= HIGHPRIO),
              "chThdSetPriority");
  chSysLock();
#if CH_USE_MUTEXES
  oldprio = currp->p_realprio;
  if ((currp->p_prio == currp->p_realprio) || (newprio > currp->p_prio))
    currp->p_prio = newprio;
  currp->p_realprio = newprio;
#else
  oldprio = currp->p_prio;
  currp->p_prio = newprio;
#endif
  chSchRescheduleS();
  chSysUnlock();
  return oldprio;
}
/**
 * @brief   Resumes a suspended thread.
 * @note    Use this function to resume threads created with @p chThdInit().
 *
 * @param[in] tp        pointer to the thread
 * @return              The pointer to the thread.
 */
Thread *chThdResume(Thread *tp) {
  chSysLock();
  chDbgAssert(tp->p_state == THD_STATE_SUSPENDED,
              "chThdResume(), #1",
              "thread not in THD_STATE_SUSPENDED state");
  chSchWakeupS(tp, RDY_OK);
  chSysUnlock();
  return tp;
}
/**
 * @brief   Requests a thread termination.
 * @note    The thread is not terminated but a termination request is added to
 *          its @p p_flags field. The thread can read this status by
 *          invoking @p chThdShouldTerminate() and then terminate cleanly.
 *
 * @param[in] tp        pointer to the thread
 */
void chThdTerminate(Thread *tp) {
  chSysLock();
  tp->p_flags |= THD_TERMINATE;
  chSysUnlock();
}
/**
 * @brief   Suspends the invoking thread for the specified time.
 *
 * @param[in] time      the delay in system ticks, the special values are
 *                      handled as follow:
 *                      - @a TIME_INFINITE the thread enters an infinite sleep
 *                        state.
 *                      - @a TIME_IMMEDIATE this value is accepted but
 *                        interpreted as a normal time specification not as an
 *                        immediate timeout specification.
 *                      .
 */
void chThdSleep(systime_t time) {
  chDbgCheck(time != TIME_INFINITE, "chThdSleep");
  chSysLock();
  chThdSleepS(time);
  chSysUnlock();
}
/**
 * @brief   Suspends the invoking thread until the system time arrives to the
 *          specified value.
 *
 * @param[in] time      absolute system time
 */
void chThdSleepUntil(systime_t time) {
  chSysLock();
  if ((time -= chTimeNow()) > 0)
    chThdSleepS(time);
  chSysUnlock();
}
/**
 * @brief   Yields the time slot.
 * @details Yields the CPU control to the next thread in the ready list with
 *          equal priority, if any.
 */
void chThdYield(void) {
  chSysLock();
  chSchDoYieldS();
  chSysUnlock();
}
/**
 * @brief   Terminates the current thread by specifying an exit status code.
 *
 * @param[in] msg       thread exit code. The code can be retrieved by using
 *                      @p chThdWait().
 */
void chThdExit(msg_t msg) {
  Thread *tp = currp;
  chSysLock();
  tp->p_u.exitcode = msg;
  THREAD_EXT_EXIT(tp);
#if CH_USE_WAITEXIT
  while (notempty(&tp->p_waiting))
    chSchReadyI(list_remove(&tp->p_waiting));
#endif
#if CH_USE_REGISTRY
  REG_REMOVE(tp);
#endif
  chSchGoSleepS(THD_STATE_FINAL);
}
#if CH_USE_DYNAMIC || defined(__DOXYGEN__)
/**
 * @brief   Adds a reference to a thread object.
 *
 * @param[in] tp        pointer to the thread
 * @return              The same thread pointer passed as parameter
 *                      representing the new reference.
 */
Thread *chThdAddRef(Thread *tp) {
  chSysLock();
  chDbgAssert(tp->p_refs < 255, "chThdAddRef(), #1", "too many references");
  tp->p_refs++;
  chSysUnlock();
  return tp;
}
/**
 * @brief   Releases a reference to a thread object.
 * @details If the references counter reaches zero and the thread
 *          is in the @p THD_STATE_FINAL state then the thread's memory is
 *          returned to the proper allocator.
 * @note    Static threads are not affected.
 *
 * @param[in] tp        pointer to the thread
 */
void chThdRelease(Thread *tp) {
  trefs_t refs;
  chSysLock();
  chDbgAssert(tp->p_refs > 0, "chThdRelease(), #1", "not referenced");
  refs = --tp->p_refs;
  chSysUnlock();
  /* If the references counter reaches zero then the memory can be returned
     to the proper allocator. Of course static threads are not affected.*/
  if (refs == 0) {
    switch (tp->p_flags & THD_MEM_MODE_MASK) {
#if CH_USE_HEAP
    case THD_MEM_MODE_HEAP:
      chHeapFree(tp);
      break;
#endif
#if CH_USE_MEMPOOLS
    case THD_MEM_MODE_MEMPOOL:
      chPoolFree(tp->p_mpool, tp);
      break;
#endif
    }
  }
}
#endif /* CH_USE_DYNAMIC */
#if CH_USE_WAITEXIT || defined(__DOXYGEN__)
/**
 * @brief   Blocks the execution of the invoking thread until the specified
 *          thread terminates then the exit code is returned.
 * @details This function waits for the specified thread to terminate then
 *          decrements its reference counter, if the counter reaches zero then
 *          the thread working area is returned to the proper allocator.
 *          The memory used by the exited thread is handled in different ways
 *          depending on the API that spawned the thread:
 *          - If the thread was spawned by @p chThdCreateStatic() or by
 *            @p chThdInit() then nothing happens and the thread working area
 *            is not released or modified in any way. This is the default,
 *            totally static, behavior.
 *          - If the thread was spawned by @p chThdCreateFromHeap() then
 *            the working area is returned to the system heap.
 *          - If the thread was spawned by @p chThdCreateFromMemoryPool()
 *            then the working area is returned to the owning memory pool.
 *          .
 *          Please read the @ref article_lifecycle article for more details.
 * @note    After invoking @p chThdWait() the thread pointer becomes invalid
 *          and must not be used as parameter for further system calls.
 * @note    The function is available only if the @p CH_USE_WAITEXIT
 *          option is enabled in @p chconf.h.
 * @note    If @p CH_USE_DYNAMIC is not specified this function just waits for
 *          the thread termination, no memory allocators are involved.
 *
 * @param[in] tp        pointer to the thread
 * @return              The exit code from the terminated thread.
 */
msg_t chThdWait(Thread *tp) {
  msg_t msg;
  chDbgCheck(tp != NULL, "chThdWait");
  chSysLock();
  chDbgAssert(tp != currp, "chThdWait(), #1", "waiting self");
#if CH_USE_DYNAMIC
  chDbgAssert(tp->p_refs > 0, "chThdWait(), #2", "not referenced");
#endif
  if (tp->p_state != THD_STATE_FINAL) {
    list_insert(currp, &tp->p_waiting);
    chSchGoSleepS(THD_STATE_WTEXIT);
  }
  msg = tp->p_u.exitcode;
  chSysUnlock();
#if CH_USE_DYNAMIC
  chThdRelease(tp);
#endif
  return msg;
}
#endif /* CH_USE_WAITEXIT */
/** @} */