/****************************************************************************
 * libs/libc/elf/elf_depend.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 <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/lib/elf.h>

#if CONFIG_LIBC_ELF_MAXDEPEND > 0

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

/****************************************************************************
 * Name: libelf_depend
 *
 * Description:
 *   Set up module dependencies between the exporter and the importer of a
 *   symbol.  The happens when the module is installed via insmod and a
 *   symbol is imported from another module.
 *
 * Returned Value:
 *   0 (OK) is returned on success and a negated errno is returned on
 *   failure.
 *
 * Assumptions:
 *   Caller holds the registry lock.
 *
 ****************************************************************************/

int libelf_depend(FAR struct module_s *importer,
                  FAR struct module_s *exporter)
{
  int freendx = -1;
  int i;

  DEBUGASSERT(importer != NULL && exporter != NULL);

  /* First checker if the exported is already in our list if dependencies.
   * This would happen if we are importing multiple symbols from the same
   * exporting module.  In that case, the module would already be in the
   * list of dependencies.
   *
   * The list dependency list is a a dumb, unpacked array of pointers.  This
   * should not be too inefficient if the number of CONFIG_LIBC_ELF_MAXDEPEND
   * is small.  Otherwise, a more dynamic data structure would be in order.
   */

  for (i = 0; i < CONFIG_LIBC_ELF_MAXDEPEND; i++)
    {
      FAR const struct module_s *modp;

      /* Check if this dependency slot is available. */

      modp = importer->dependencies[i];
      if (modp == NULL)
        {
          /* Remember this slot for use the module is NOT already in the
           * list of dependencies.
           */

          freendx = i;
        }
      else if (modp == exporter)
        {
          /* Yes, we are already importing symbols from this module.  Nothing
           * more needs to be done.
           */

          return OK;
        }
    }

  /* If we get here, then (1) this is a new exporting module that does not
   * already appear in the list of dependencies, and (2) freendx is the
   * index to the last free slot in the dependency list.  If freendx is
   * negative, then the dependency list is full.
   */

  if (freendx >= 0)
    {
      /* Increment the count of dependencies on the exporter module */

      DEBUGASSERT(exporter->dependents < UINT8_MAX);
      if (exporter->dependents >= UINT8_MAX)
        {
          return -ENOSPC;
        }

      exporter->dependents++;

      /* And remember the exporter so that we can decrement the count of
       * dependents if the importer is removed.
       */

      DEBUGASSERT(importer->dependencies[freendx] == NULL);
      importer->dependencies[freendx] = exporter;
      return OK;
    }

  /* If we get there then the list of dependencies is full. */

  DEBUGPANIC();
  return -ENFILE;
}

/****************************************************************************
 * Name: libelf_undepend
 *
 * Description:
 *   Tear down module dependencies between the exporters and the importer of
 *   symbols.  This happens when the module is removed via rmmod (and on
 *   error handling cases in insmod).
 *
 * Returned Value:
 *   0 (OK) is returned on success and a negated errno is returned on
 *   failure.
 *
 * Assumptions:
 *   Caller holds the registry lock.
 *
 ****************************************************************************/

int libelf_undepend(FAR struct module_s *importer)
{
  FAR struct module_s *exporter;
  int i;

  DEBUGASSERT(importer != NULL && importer->dependents == 0);

  /* Decrement the dependency count on each of exporters of symbols used by
   * this importer module.  This is an unpacked array of pointers.  This
   * should not be too inefficient if the number of CONFIG_LIBC_ELF_MAXDEPEND
   * is small.  Otherwise, a more dynamic data structure would be in order.
   */

  for (i = 0; i < CONFIG_LIBC_ELF_MAXDEPEND; i++)
    {
      exporter = importer->dependencies[i];
      if (exporter != NULL)
        {
          DEBUGASSERT(exporter->dependents > 0);
          if (exporter->dependents > 0)
            {
              exporter->dependents--;
            }

          importer->dependencies[i] = NULL;
        }
    }

  return OK;
}

#endif
