/****************************************************************************
 * sched/signal/sig_notification.c
 *
 * 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 <inttypes.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <debug.h>

#include <nuttx/signal.h>

#include "sched/sched.h"
#include "signal/signal.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#ifdef CONFIG_SIG_EVTHREAD_HPWORK
#  define SIG_EVTHREAD_WORK HPWORK
#else
#  define SIG_EVTHREAD_WORK LPWORK
#endif

/****************************************************************************
 * Name: nxsig_notification_worker
 *
 * Description:
 *   Perform the callback from the context of the worker thread.
 *
 * Input Parameters:
 *   arg - Work argument.
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

#ifdef CONFIG_SIG_EVTHREAD
static void nxsig_notification_worker(FAR void *arg)
{
  FAR struct sigwork_s *work = (FAR struct sigwork_s *)arg;

  DEBUGASSERT(work != NULL);

  /* Perform the callback */

  work->func(work->value);
}

#endif /* CONFIG_SIG_EVTHREAD */

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

/****************************************************************************
 * Name: nxsig_notification
 *
 * Description:
 *   Notify a client an event via either a signal or function call
 *   base on the sigev_notify field.
 *
 * Input Parameters:
 *   pid   - The task/thread ID a the client thread to be signaled.
 *   event - The instance of struct sigevent that describes how to signal
 *           the client.
 *   code  - Source: SI_USER, SI_QUEUE, SI_TIMER, SI_ASYNCIO, or SI_MESGQ
 *   work  - The work structure to queue.  Must be non-NULL if
 *           event->sigev_notify == SIGEV_THREAD.  Ignored if
 *           CONFIG_SIG_EVTHREAD is not defined.
 *
 * Returned Value:
 *   This is an internal OS interface and should not be used by applications.
 *   It follows the NuttX internal error return policy:  Zero (OK) is
 *   returned on success.  A negated errno value is returned on failure.
 *
 ****************************************************************************/

int nxsig_notification(pid_t pid, FAR struct sigevent *event,
                       int code, FAR struct sigwork_s *work)
{
  sinfo("pid=%" PRIu16 " signo=%d code=%d sival_ptr=%p\n",
        pid, event->sigev_signo, code, event->sigev_value.sival_ptr);

  /* Notify client via a signal? */

  if (event->sigev_notify == SIGEV_SIGNAL)
    {
#ifdef CONFIG_SCHED_HAVE_PARENT
      FAR struct tcb_s *rtcb = this_task();
#endif
      siginfo_t info;

      /* Yes.. Create the siginfo structure */

      info.si_signo  = event->sigev_signo;
      info.si_code   = code;
      info.si_errno  = OK;
#ifdef CONFIG_SCHED_HAVE_PARENT
      info.si_pid    = rtcb->pid;
      info.si_status = OK;
#endif

      /* Some compilers (e.g., SDCC), do not permit assignment of aggregates.
       * Use of memcpy() is overkill;  We could just copy the larger of the
       * int and FAR void * members in the union.  memcpy(), however, does
       * not require that we know which is larger.
       */

      memcpy(&info.si_value, &event->sigev_value, sizeof(union sigval));

      /* Send the signal */

      return nxsig_dispatch(pid, &info);
    }

#ifdef CONFIG_SIG_EVTHREAD
  /* Notify the client via a function call */

  else if (event->sigev_notify == SIGEV_THREAD)
    {
      /* Initialize the work information */

      work->value = event->sigev_value;
      work->func  = event->sigev_notify_function;

      /* Then queue the work */

      return work_queue(SIG_EVTHREAD_WORK, &work->work,
                        nxsig_notification_worker, work, 0);
    }
#endif

  return event->sigev_notify == SIGEV_NONE ? OK : -ENOSYS;
}

/****************************************************************************
 * Name: nxsig_cancel_notification
 *
 * Description:
 *   Cancel the notification if it doesn't send yet.
 *
 * Input Parameters:
 *   work  - The work structure to cancel
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef CONFIG_SIG_EVTHREAD
void nxsig_cancel_notification(FAR struct sigwork_s *work)
{
  work_cancel_sync(SIG_EVTHREAD_WORK, &work->work);
}
#endif