/*
 *  COPYRIGHT (c) 2019 Chris Johns <chrisj@rtems.org>
 *
 *  The license and distribution terms for this file may be
 *  found in the file LICENSE in this distribution or at
 *  http://www.rtems.org/license/LICENSE.
 */
/**
 * @file
 *
 * @ingroup rtems_rtld
 *
 * @brief RTEMS Run-Time Link Editor Bit Allocator
 *
 * A configurable allocator for small peices of memory where embedding
 * control blocks into the memory are not viable. The amount of memory
 * should be small and the minimum allocator a 32bit work or higher.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>

#include <rtems/rtl/rtl-allocator.h>
#include "rtl-bit-alloc.h"
#include "rtl-error.h"
#include <rtems/rtl/rtl-trace.h>

#define BITS_PER_WORD (sizeof (uint32_t) * 8)

static size_t
bit_blocks (rtems_rtl_bit_alloc* balloc, size_t size)
{
  if (size == 0)
    return 0;
  return ((size - 1) / balloc->block_size) + 1;
}

static size_t
bit_word (size_t bit)
{
  return bit / BITS_PER_WORD;
}

static size_t
bit_offset (size_t bit)
{
  return bit % (sizeof (uint32_t) * 8);
}

static uint32_t
bit_mask (size_t bit)
{
  return 1 << bit_offset (bit);
}

static void
bit_set (rtems_rtl_bit_alloc* balloc, size_t start, size_t bits)
{
  size_t b;
  for (b = start; b < (start + bits); ++b)
    balloc->bits[bit_word (b)] |= bit_mask (b);
}

static void
bit_clear (rtems_rtl_bit_alloc* balloc, size_t start, size_t bits)
{
  size_t b;
  for (b = start; b < (start + bits); ++b)
    balloc->bits[bit_word (b)] &= ~bit_mask (b);
}

static ssize_t
bit_find_clear (rtems_rtl_bit_alloc* balloc, size_t blocks)
{
  size_t base = 0;
  size_t clear = 0;
  size_t b;

  for (b = 0; b < balloc->blocks; ++b)
  {
    if (balloc->bits[b] != 0xffffffff)
    {
      uint32_t word = balloc->bits[b];
      size_t   o;
      for (o = 0; o < BITS_PER_WORD; ++o, word >>= 1)
      {
        if ((word & 1) == 0)
        {
          if (clear == 0)
            base = (b * BITS_PER_WORD) + o;
          ++clear;
          if (clear == blocks)
            return base;
        }
        else
        {
          clear = 0;
          base = 0;
        }
      }
    }
    else
    {
      clear = 0;
      base = 0;
    }
  }

  return -1;
}

rtems_rtl_bit_alloc*
rtems_rtl_bit_alloc_open (void* base, size_t size, size_t block_size, size_t used)
{
  rtems_rtl_bit_alloc* balloc;
  const size_t         base_size = base == NULL ? size : 0;;
  const size_t         blocks = (size / block_size) / BITS_PER_WORD;
  const size_t         bit_bytes = blocks * sizeof(uint32_t);

  if (rtems_rtl_trace (RTEMS_RTL_TRACE_BIT_ALLOC))
    printf ("rtl: balloc: open: base=%p size=%zu" \
            " block-size=%zu blocks=%zu used=%zu\n",
            base, size, block_size, blocks, used);

  if (size == 0)
  {
    rtems_rtl_set_error (ENOMEM, "bit allocator size is 0");
    return false;
  }

  if (used > size)
  {
    rtems_rtl_set_error (ENOMEM, "bad bit allocator used value");
    return false;
  }

  balloc = rtems_rtl_alloc_new (RTEMS_RTL_ALLOC_OBJECT,
                                sizeof (rtems_rtl_bit_alloc) + bit_bytes + base_size,
                                true);
  if (balloc == NULL)
  {
    rtems_rtl_set_error (ENOMEM, "not bit allocator memory");
    return NULL;
  }

  balloc->base =
    base == NULL ? ((void*) balloc) + sizeof (rtems_rtl_bit_alloc) + bit_bytes: base;
  balloc->size = size;
  balloc->bits = ((void*) balloc) + sizeof (rtems_rtl_bit_alloc);
  balloc->block_size = block_size;
  balloc->blocks = blocks;

  bit_set (balloc, 0, bit_blocks (balloc, used));

  return balloc;
}

void
rtems_rtl_bit_alloc_close (rtems_rtl_bit_alloc* balloc)
{
  if (rtems_rtl_trace (RTEMS_RTL_TRACE_BIT_ALLOC))
    printf ("rtl: balloc: close: base=%p size=%zu\n",
            balloc->base, balloc->size);
  rtems_rtl_alloc_del (RTEMS_RTL_ALLOC_OBJECT, balloc);
}

void*
rtems_rtl_bit_alloc_balloc (rtems_rtl_bit_alloc* balloc, size_t size)
{
  size_t  blocks = bit_blocks (balloc, size);
  ssize_t block = bit_find_clear (balloc, blocks);
  if (block < 0)
    return NULL;
  if (rtems_rtl_trace (RTEMS_RTL_TRACE_BIT_ALLOC))
    printf ("rtl: balloc: balloc: size=%zu blocks=%zu block=%zi\n",
            size, blocks, block);
  bit_set (balloc, block, blocks);
  return (void*) (balloc->base + (block * balloc->block_size));
}

void
rtems_rtl_bit_alloc_bfree (rtems_rtl_bit_alloc* balloc, void* addr, size_t size)
{
  const uint8_t* a = (const uint8_t*) addr;
  if (addr != NULL && a >= balloc->base && a < balloc->base + balloc->size)
  {
    size_t block = bit_blocks (balloc, a - balloc->base);
    size_t blocks = bit_blocks (balloc, size);
    bit_clear (balloc, block, blocks);
  }
}
