/****************************************************************************
 * wireless/ieee802154/ieee802154_primitive.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 <stdint.h>
#include <string.h>
#include <assert.h>
#include <debug.h>

#include <nuttx/kmalloc.h>
#include <nuttx/wireless/ieee802154/ieee802154_mac.h>

#include "mac802154.h"

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

/* NOTE:  The CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE options is marked as
 * marked 'experimental' and with the default 0 zero because there are no
 * interrupt level allocations performed by the current IEEE 802.15.4 MAC
 * code.
 */

#if !defined(CONFIG_IEEE802154_PRIMITIVE_PREALLOC) || \
    CONFIG_IEEE802154_PRIMITIVE_PREALLOC < 0
#  undef CONFIG_IEEE802154_PRIMITIVE_PREALLOC
#  define CONFIG_IEEE802154_PRIMITIVE_PREALLOC 20
#endif

#if !defined(CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE) || \
    CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE < 0
#  undef CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE
#  define CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE 0
#endif

#if CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE > CONFIG_IEEE802154_PRIMITIVE_PREALLOC
#  undef CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE
#  define CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE CONFIG_IEEE802154_PRIMITIVE_PREALLOC
#endif

/* Memory Pools */

#define POOL_PRIMITIVE_GENERAL  0
#define POOL_PRIMITIVE_IRQ      1
#define POOL_PRIMITIVE_DYNAMIC  2

/****************************************************************************
 * Private Data Types
 ****************************************************************************/

/* Private data type that extends the ieee802154_primitive_s struct */

struct ieee802154_priv_primitive_s
{
  /* Must be first member so we can cast to/from */

  struct ieee802154_primitive_s pub;
  FAR struct ieee802154_priv_primitive_s *flink;
  uint8_t pool;
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

#if CONFIG_IEEE802154_PRIMITIVE_PREALLOC > 0
#if CONFIG_IEEE802154_PRIMITIVE_PREALLOC > CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE
/* The g_primfree is a list of primitive structures that are available for
 * general use.  The number of messages in this list is a system
 * configuration item.
 */

static struct ieee802154_priv_primitive_s *g_primfree;
#endif

#if CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE > 0
/* The g_primfree_irq is a list of primitive structures that are reserved for
 * use by only by interrupt handlers.
 */

static struct ieee802154_priv_primitive_s *g_primfree_irq;
#endif

/* Pool of pre-allocated primitive structures */

static struct ieee802154_priv_primitive_s
                g_primpool[CONFIG_IEEE802154_PRIMITIVE_PREALLOC];
#endif /* CONFIG_IEEE802154_PRIMITIVE_PREALLOC > 0 */

static bool g_poolinit = false;

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

/****************************************************************************
 * Name: ieee802154_primitivepool_initialize
 *
 * Description:
 *   This function initializes the primitive allocator.  This function must
 *   be called early in the initialization sequence before any radios
 *   begin operation.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void ieee802154_primitivepool_initialize(void)
{
  /* Only allow the pool to be initialized once */

  if (g_poolinit)
    {
      return;
    }

  g_poolinit = true;

#if CONFIG_IEEE802154_PRIMITIVE_PREALLOC > 0
  FAR struct ieee802154_priv_primitive_s *pool = g_primpool;
  int remaining = CONFIG_IEEE802154_PRIMITIVE_PREALLOC;

#if CONFIG_IEEE802154_PRIMITIVE_PREALLOC > CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE
  /* Initialize g_primfree, the list of primitive structures that are
   * available for general use.
   */

  g_primfree = NULL;
  while (remaining > CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE)
    {
      FAR struct ieee802154_priv_primitive_s *prim = pool;

      /* Add the next meta data structure from the pool to the list of
       * general structures.
       */

      prim->flink = g_primfree;
      g_primfree  = prim;

      /* Set up for the next structure from the pool */

      pool++;
      remaining--;
    }
#endif

#if CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE > 0
  /* Initialize g_primfree_irq is a list of primitive structures reserved for
   * use by only by interrupt handlers.
   */

  g_primfree_irq = NULL;
  while (remaining > 0)
    {
      FAR struct ieee802154_priv_primitive_s *prim = pool;

      /* Add the next meta data structure from the pool to the list of
       * general structures.
       */

      prim->flink    = g_primfree_irq;
      g_primfree_irq = prim;

      /* Set up for the next structure from the pool */

      pool++;
      remaining--;
    }
#endif
#endif /* CONFIG_IEEE802154_PRIMITIVE_PREALLOC > 0 */
}

/****************************************************************************
 * Name: ieee802154_primitive_allocate
 *
 * Description:
 *   The ieee802154_primitive_allocate function will get a free primitive
 *   structure for use by the IEEE 802.15.4 MAC.
 *
 *   Interrupt handling logic will first attempt to allocate from the
 *   g_primfree list.  If that list is empty, it will attempt to allocate
 *   from its reserve, g_primfree_irq.  If that list is empty, then the
 *   allocation fails (NULL is returned).
 *
 *   Non-interrupt handler logic will attempt to allocate from g_primfree
 *   list.  If that the list is empty, then the primitive structure will be
 *   allocated from the dynamic memory pool.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   A reference to the allocated primitive structure.
 *   All user fields in this structure have been zeroed.
 *   On a failure to allocate, NULL is returned.
 *
 ****************************************************************************/

FAR struct ieee802154_primitive_s *ieee802154_primitive_allocate(void)
{
#if CONFIG_IEEE802154_PRIMITIVE_PREALLOC > 0
  FAR struct ieee802154_priv_primitive_s *prim;
  irqstate_t flags;
  uint8_t pool;

  /* If we were called from an interrupt handler, then try to get the meta-
   * data structure from generally available list of messages. If this fails,
   * then try the list of messages reserved for interrupt handlers
   */

  flags = enter_critical_section(); /* Always necessary in SMP mode */
  if (up_interrupt_context())
    {
#if CONFIG_IEEE802154_PRIMITIVE_PREALLOC > CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE
      /* Try the general free list */

      if (g_primfree != NULL)
        {
          prim           = g_primfree;
          g_primfree     = prim->flink;

          leave_critical_section(flags);
          pool          = POOL_PRIMITIVE_GENERAL;
        }
      else
#endif
#if CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE > 0
      /* Try the list list reserved for interrupt handlers */

      if (g_primfree_irq != NULL)
        {
          prim           = g_primfree_irq;
          g_primfree_irq = prim->flink;

          leave_critical_section(flags);
          pool          = POOL_PRIMITIVE_IRQ;
        }
      else
#endif
        {
          leave_critical_section(flags);
          return NULL;
        }
    }

  /* We were not called from an interrupt handler. */

  else
    {
#if CONFIG_IEEE802154_PRIMITIVE_PREALLOC > CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE
      /* Try the general free list */

      if (g_primfree != NULL)
        {
          prim           = g_primfree;
          g_primfree     = prim->flink;

          leave_critical_section(flags);
          pool          = POOL_PRIMITIVE_GENERAL;
        }
      else
#endif
        {
          /* If we cannot a primitive structure from the free list, then we
           * will have to allocate one from the kernel memory pool.
           */

          leave_critical_section(flags);
          prim = (FAR struct ieee802154_priv_primitive_s *)
            kmm_malloc((sizeof (struct ieee802154_priv_primitive_s)));

          /* Check if we allocated the primitive structure */

          if (prim == NULL)
            {
              /* No..  memory not available */

              wlerr("ERROR: Failed to allocate primitive.\n");
              return NULL;
            }

          /* Remember that this primitive structure
           * was dynamically allocated
           */

          pool = POOL_PRIMITIVE_DYNAMIC;
        }
    }

  /* We have successfully allocated memory from some source.
   * Zero and tag the allocated primitive structure.
   */

  prim->pool = pool;
  memset(&prim->pub, 0, sizeof(struct ieee802154_primitive_s));

  wlinfo("Primitive allocated: %p\n", prim);
  return &prim->pub;
#else
  return NULL;
#endif
}

/****************************************************************************
 * Name: ieee802154_primitive_free
 *
 * Description:
 *   The ieee802154_primitive_free function will return a primitive structure
 *   to the free pool of  messages if it was a pre-allocated primitive
 *   structure. If the primitive structure was allocated dynamically it will
 *   be deallocated.
 *
 * Input Parameters:
 *   prim - primitive structure to free
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void ieee802154_primitive_free(FAR struct ieee802154_primitive_s *prim)
{
  if (--prim->nclients > 0)
    {
      wlinfo("Remaining Clients: %d\n", prim->nclients);
      return;
    }

#if CONFIG_IEEE802154_PRIMITIVE_PREALLOC > 0
  irqstate_t flags;
  FAR struct ieee802154_priv_primitive_s *priv =
    (FAR struct ieee802154_priv_primitive_s *)prim;

#if CONFIG_IEEE802154_PRIMITIVE_PREALLOC > CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE
  /* If this is a generally available pre-allocated primitive structure,
   * then just put it back in the free list.
   */

  if (priv->pool == POOL_PRIMITIVE_GENERAL)
    {
      /* Make sure we avoid concurrent access to the free
       * list from interrupt handlers.
       */

      flags = enter_critical_section();
      priv->flink = g_primfree;
      g_primfree  = priv;
      leave_critical_section(flags);
    }
  else
#endif

#if CONFIG_IEEE802154_PRIMITIVE_IRQRESERVE > 0
  /* If this is a primitive structure pre-allocated for interrupts,
   * then put it back in the correct free list.
   */

  if (priv->pool == POOL_PRIMITIVE_IRQ)
    {
      /* Make sure we avoid concurrent access to the free
       * list from interrupt handlers.
       */

      flags = enter_critical_section();
      priv->flink    = g_primfree_irq;
      g_primfree_irq  = priv;
      leave_critical_section(flags);
    }
  else
#endif

    {
      /* Otherwise, deallocate it. */

      DEBUGASSERT(priv->pool == POOL_PRIMITIVE_DYNAMIC);
      kmm_free(priv);
    }
#endif

  wlinfo("Primitive freed: %p\n", prim);
}