/****************************************************************************
 * sched/task/task_init.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <stdint.h>
#include <sched.h>
#include <assert.h>
#include <errno.h>

#include <nuttx/arch.h>
#include <nuttx/queue.h>
#include <nuttx/addrenv.h>
#include <nuttx/sched.h>
#include <nuttx/trace.h>
#include <nuttx/semaphore.h>
#include <nuttx/environ.h>

#include "sched/sched.h"
#include "group/group.h"
#include "task/task.h"
#include "tls/tls.h"

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: task_setup
 *
 * Description: setup and configures the child task's TCB.
 *
 * Input Parameters:
 *   tcb        - Address of the new task's TCB
 *   name       - Name of the new task (not used)
 *   entry      - Application start point of the new task
 *   actions    - File action to be performed.
 *   attr       - Use attr set stacksize, stackaddr and priority.
 *   argv       - A pointer to an array of input parameters.
 *   ttype      - task tcb flag type, KERNEL or TASK
 *
 * Returned Value:
 *   This is a NuttX internal function so it follows the convention that
 *   0 (OK) is returned on success and a negated is returned on failure.
 *
 ****************************************************************************/

static int task_setup(FAR struct tcb_s *tcb, const char *name, main_t entry,
                      FAR const posix_spawn_file_actions_t *actions,
                      FAR const posix_spawnattr_t *attr,
                      FAR char * const argv[], FAR char * const envp[],
                      int ttype)
{
  size_t stacksize = attr->stacksize;
#ifndef CONFIG_BUILD_KERNEL
  FAR void *stack = attr->stackaddr;
#else
  FAR void *stack = NULL;
#endif
  int priority = attr->priority;
  int ret;

  /* Duplicate the parent tasks environment */

  ret = env_dup(tcb->group->tg_info, envp);
  if (ret >= 0)
    {
      /* Associate file descriptors with the new task */

      ret = group_setuptaskfiles(tcb, actions, true);
      if (ret >= 0)
        {
          /* Set the task name */

          nxtask_setup_name(tcb, name);

          if (stack)
            {
              /* Use pre-allocated stack */

              ret = up_use_stack(tcb, stack, stacksize, ttype);
            }
          else
            {
              /* Allocate the stack for the TCB */

              ret = up_create_stack(tcb, stacksize, ttype);
            }

          if (ret >= OK)
            {
              /* Initialize thread local storage */

              ret = tls_init_info(tcb);
              if (ret >= OK)
                {
#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_ARCH_KERNEL_STACK)
                  /* Allocate the kernel stack */

                  if (ttype != TCB_FLAG_TTYPE_KERNEL)
                    {
                      ret = up_addrenv_kstackalloc(tcb);
                    }
#endif

                  if (ret >= OK)
                    {
                      /* Setup to pass parameters to the new task */

                      ret = nxtask_setup_stackargs(tcb, name, argv);
                      if (ret >= OK)
                        {
                          /* Initialize the task control block */

                          ret = nxtask_setup_scheduler(tcb, priority,
                                                       nxtask_start,
                                                       entry, ttype);
                        }
                    }
                }
            }

          if (ret < OK && !stack && tcb->stack_alloc_ptr)
            {
#ifdef CONFIG_BUILD_KERNEL
              /* If the exiting thread is not a kernel thread, then it has
               * an address environment. Don't bother to release the stack
               * memory in this case... There is no point since the memory
               * lies in the user memory region that will be destroyed anyway
               * (and the address environment has probably already been
               * destroyed at this point.. so we would crash if we even tried
               * it).  But if this is a privileged group, then we still have
               * to release the memory using the kernel allocator.
               */

              if (ttype == TCB_FLAG_TTYPE_KERNEL)
#endif
                {
                  up_release_stack(tcb, ttype);
                }
            }
        }
    }

  return ret;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: nxtask_init
 *
 * Description:
 *   This function initializes a Task Control Block (TCB) in preparation for
 *   starting a new thread.  It performs a subset of the functionality of
 *   task_create()
 *
 *   Unlike task_create():
 *     1. Allocate the TCB.  The pre-allocated TCB is passed in argv.
 *     2. Allocate the stack.  The pre-allocated stack is passed in argv.
 *     3. Activate the task. This must be done by calling nxtask_activate().
 *
 *   Certain fields of the pre-allocated TCB may be set to change the
 *   nature of the created task.  For example:
 *
 *     - Task type may be set in the TCB flags to create kernel thread
 *
 * Input Parameters:
 *   tcb        - Address of the new task's TCB
 *   name       - Name of the new task (not used)
 *   entry      - Application start point of the new task
 *   actions    - File action to be performed.
 *   attr       - Use attr set stacksize, stackaddr and priority.
 *   argv       - A pointer to an array of input parameters.  The array
 *                should be terminated with a NULL argv[] value. If no
 *                parameters are required, argv may be NULL.
 *
 * Returned Value:
 *   OK on success; negative error value on failure appropriately.  (See
 *   nxtask_setup_scheduler() for possible failure conditions).  On failure,
 *   the caller is responsible for freeing the stack memory and for calling
 *   nxsched_release_tcb() to free the TCB (which could be in most any
 *   state).
 *
 ****************************************************************************/

int nxtask_init(FAR struct tcb_s *tcb, const char *name, main_t entry,
                FAR const posix_spawn_file_actions_t *actions,
                FAR const posix_spawnattr_t *attr,
                FAR char * const argv[], FAR char * const envp[])
{
  int ttype = atomic_read(&tcb->flags) & TCB_FLAG_TTYPE_MASK;
#ifdef CONFIG_MM_TASK_HEAP
  size_t heapsize = attr->heapsize;
#else
  size_t heapsize = 0;
#endif
  int ret = OK;

  sched_trace_begin();

#ifndef CONFIG_DISABLE_PTHREAD
  /* Only tasks and kernel threads can be initialized in this way */

  DEBUGASSERT(tcb && ttype != TCB_FLAG_TTYPE_PTHREAD);
#endif

#ifdef CONFIG_ARCH_ADDRENV
  /* Kernel threads do not own any address environment */

  if (ttype == TCB_FLAG_TTYPE_TASK)
    {
      FAR struct task_group_s *group =
        (FAR struct task_group_s *)(tcb + 1);

      if (group->tg_addrenv_own == NULL)
        {
          FAR struct addrenv_s *addrenv = addrenv_allocate();
          if (addrenv == NULL)
            {
              ret = -ENOMEM;
            }
          else
            {
              addrenv_attach(tcb, addrenv);
            }
        }
    }

#endif

  if (ret == OK)
    {
      /* Create a new task group */

      ret = group_initialize(tcb, atomic_read(&tcb->flags), heapsize);
      if (ret >= 0)
        {
#ifndef CONFIG_DISABLE_PTHREAD
          /* Initialize the task join */

          nxtask_joininit(tcb);
#endif

#ifdef CONFIG_SCHED_NOPREEMPT_KERNEL
          if (ttype == TCB_FLAG_TTYPE_KERNEL)
            {
              /* Disable pre-emption for this task */

              tcb->lockcount = 1;
            }
#endif

          nxsem_init(&tcb->exit_sem, 0, 0);

          ret = task_setup(tcb, name, entry, actions, attr, argv,
                           envp, ttype);
          if (ret >= 0)
            {
              /* Now we have enough in place that we can join the group */

              group_postinitialize(tcb);
            }
          else
            {
              nxtask_joindestroy(tcb);

              nxsem_destroy(&tcb->exit_sem);

              group_leave(tcb);

#ifdef CONFIG_ARCH_ADDRENV
              addrenv_drop(tcb->group->tg_addrenv_own, false);
#endif
            }
        }
    }

  sched_trace_end();
  return ret;
}

/****************************************************************************
 * Name: nxtask_uninit
 *
 * Description:
 *   Undo all operations on a TCB performed by task_init() and release the
 *   TCB by calling kmm_free().  This is intended primarily to support
 *   error recovery operations after a successful call to task_init()
 *   when a subsequent call to task_activate fails.
 *
 *   Caution:  Freeing of the TCB itself might be an unexpected side-effect.
 *
 * Input Parameters:
 *   tcb - Address of the TCB initialized by task_init()
 *
 * Returned Value:
 *   OK on success; negative error value on failure appropriately.
 *
 ****************************************************************************/

void nxtask_uninit(FAR struct tcb_s *tcb)
{
  irqstate_t flags;

  /* The TCB was added to the inactive task list by
   * nxtask_setup_scheduler().
   */

  flags = enter_critical_section();
  dq_rem((FAR dq_entry_t *)tcb, list_inactivetasks());
  leave_critical_section(flags);

  /* Release all resources associated with the TCB... Including the TCB
   * itself.
   */

  nxsched_release_tcb(tcb, atomic_read(&tcb->flags) & TCB_FLAG_TTYPE_MASK);
}