/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed 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.
 */
/***************************************************/
/* File   : RMA.c                                  */
/*                                                 */
/* Purpose: The browser needs to claim RMA space   */
/*          for some of the grottier protocols out */
/*          there now and again. There are a       */
/*          sufficiently large number of claims to */
/*          warrant a thin veneer over claim and   */
/*          release of RMA to keep track of blocks */
/*          and make sure leaks don't occur.       */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 24-Oct-97: Created.                    */
/***************************************************/

#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

#include "swis.h"

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "svcprint.h"
#include "Global.h"
#include "Utils.h"

#include "RMA.h"

/* Structures */

typedef struct rma_array_item
{
  unsigned int   flags;     /* Currently undefined (0) */
  browser_data * allocator;
  void         * rma_block;
}
rma_array_item;

/* Locals */

rma_array_item * rma_array          = NULL;
int              rma_array_elements = 0;

/* Static function prototypes */

static _kernel_oserror * rma_add_item    (int * new_item);
static _kernel_oserror * rma_remove_item (int item);

/*************************************************/
/* rma_claim()                                   */
/*                                               */
/* Claims a block of RMA, recording the usage by */
/* adding an item to the rma_array_item array.   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             for which the claim is taking     */
/*             place, or NULL for a misc block - */
/*             in that case the block can only   */
/*             be identified for later freeing   */
/*             by its address;                   */
/*                                               */
/*             Size of the block to allocate, in */
/*             bytes;                            */
/*                                               */
/*             Pointer to a void * which will be */
/*             filled in with the address of the */
/*             new block.                        */
/*                                               */
/* Assumes:    The void * pointer is not NULL.   */
/*************************************************/

_kernel_oserror * rma_claim(browser_data * allocator, int size, void ** rma_block)
{
  _kernel_oserror * e;
  int               new_item;

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_claim: Called for allocator %p, block size %d\n",allocator,size);
  #endif

  if (rma_block) *rma_block = NULL;
  else return NULL;

  /* First add an array item to hold details on the RMA claim */

  RetError(rma_add_item(&new_item));

  /* The above should return an error if allocation fails, */
  /* but 'just in case'...                                 */

  if (new_item == -1) return make_no_memory_error(22);

  /* Fill it in, claiming the RMA space in passing */

  rma_array[new_item].flags     = 0;
  rma_array[new_item].allocator = allocator;

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_claim: \0211Allocating %d bytes RMA\0217\n",size);
  #endif

  e = _swix(OS_Module,
            _IN(0) | _IN(3) | _OUT(2),

            6,
            size,

            &rma_array[new_item].rma_block);

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_claim: \0211Been given block %p\0217\n",rma_array[new_item].rma_block);
  #endif

  /* If the claim appears to have failed, free the array item */
  /* again and return a generic out of memory error.          */

  if (!rma_array[new_item].rma_block)
  {
    rma_remove_item(new_item);
    return make_no_memory_error(22);
  }

  /* Otherwise, we've finished */

  if (rma_block) *rma_block = rma_array[new_item].rma_block;

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_claim: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* rma_release()                                 */
/*                                               */
/* Releases (frees) one or several blocks of RMA */
/* claimed by rma_claim, identified by block     */
/* address or an owning browser.                 */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             for which the claim originally    */
/*             took place to free all blocks     */
/*             claimed for that browser - this   */
/*             is ignored if the second          */
/*             parameter is non-NULL (see        */
/*             below);                           */
/*                                               */
/*             Address of the block - this is    */
/*             always used in preference to the  */
/*             browser pointer, so it *must* be  */
/*             NULL if all blocks for a given    */
/*             browser are to be freed.          */
/*************************************************/

_kernel_oserror * rma_release(browser_data * allocator, void * rma_block)
{
  int search, found, found_any = 0;

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_release: Called for allocator %p, block %p\n",allocator,rma_block);
  #endif

  /* Sanity check */

  if (!allocator && !rma_block)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      StrNCpy0(erb.errmess,
               "Null allocator and RMA block pointer given to rma_release");

      return &erb;

    #endif

    return NULL;
  }

  /* If we've been given a specific block, search by that. Otherwise, */
  /* remove all items owned by the given browser.                     */

  if (rma_block)
  {
    do
    {
      found  = 0;
      search = 0;

      while (search < rma_array_elements)
      {
        if (rma_array[search].rma_block == rma_block)
        {
          /* If an allocator was given, and the block doesn't */
          /* match it, then warn about this in TRACE builds.  */

          #ifdef TRACE

            if (allocator && rma_array[search].allocator != allocator)
            {
              erb.errnum = Utils_Error_Custom_Normal;

              sprintf(erb.errmess,
                      "Going to free block %p for browser %p, though the block's allocator was %p, in rma_release",
                      rma_block,
                      allocator,
                      rma_array[search].allocator);

              show_error_ret(&erb);
            }

          #endif

          /* Now free the block */

          rma_remove_item(search);
          found = found_any = 1;
          break;
        }

        search++;
      }
    }
    while (found);
  }
  else
  {
    do
    {
      found  = 0;
      search = 0;

      while (search < rma_array_elements)
      {
        if (rma_array[search].allocator == allocator)
        {
          rma_remove_item(search);
          found = found_any = 1;
          break;
        }

        search++;
      }
    }
    while (found);
  }

  /* Trace builds will complain if no item was found */
  /* - found_any isn't used for non-TRACE builds at  */
  /* the moment but could be in future, and so is    */
  /* unconditionally compiled in.                    */

  #ifdef TRACE

    if (!found_any)
    {
      erb.errnum = Utils_Error_Custom_Message;

      sprintf(erb.errmess,
              "Didn't find the item for allocator %p, address %p in rma_release",
              allocator,
              rma_block);

      show_error_ret(&erb);
    }

  #endif

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_release: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* rma_shutdown()                                */
/*                                               */
/* Frees all items in array of claimed blocks,   */
/* and all associated RMA blocks.                */
/*                                               */
/* TRACE builds will complain if there are any   */
/* items to free, as usually other processes     */
/* should have tidied up and this function       */
/* will have nothing to do except exit.          */
/*************************************************/

void rma_shutdown(void)
{
  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_shutdown: Called\n");
  #endif

  if (rma_array_elements)
  {
    int item;

    #ifdef TRACE

      if (tl & (1u<<12)) Printf("rma_shutdown: Have item(s) to free\n");

      erb.errnum = Utils_Error_Custom_Message;

      if (rma_array_elements == 1)
      {
        StrNCpy0(erb.errmess,
                 "Possible RMA leak; rma_shutdown has a block to free");
      }
      else
      {
        sprintf(erb.errmess,
                "Possible RMA leak; rma_shutdown has %d blocks to free",
                rma_array_elements);
      }

      show_error_ret(&erb);

    #endif

    /* Free all of the RMA blocks */

    for (item = 0; item < rma_array_elements; item++)
    {
      if (rma_array[item].rma_block)
      {
        _swix(OS_Module,
              _IN(0) | _IN(2),

              7,
              rma_array[item].rma_block);
      }
    }

    /* Free the array itself */

    free(rma_array);

    rma_array          = NULL;
    rma_array_elements = 0;
  }

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_shutdown: Successful\n");
  #endif

  return;
}

/*************************************************/
/* rma_add_item()                                */
/*                                               */
/* Adds an rma_array_item struct to the array of */
/* such structures. The contents are not         */
/* initialised.                                  */
/*                                               */
/* Parameters: Pointer to an int, in which the   */
/*             array index of the new item is    */
/*             placed, or -1 for an error - this */
/*             will be in addition to an error   */
/*             returned directly.                */
/*                                               */
/* Assumes:    The pointer may not be NULL.      */
/*************************************************/

static _kernel_oserror * rma_add_item(int * new_item)
{
  rma_array_item * new_array;

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_add_item: Called\n");
  #endif

  if (!new_item)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      StrNCpy0(erb.errmess,
               "Null new_item int pointer passed to rma_add_item");

      return &erb;

    #endif

    return NULL;
  }

  if (!rma_array_elements) new_array = malloc (sizeof(rma_array_item));
  else                     new_array = realloc(rma_array, sizeof(rma_array_item) * (rma_array_elements + 1));

  if (!new_array) return make_no_memory_error(22);
  else rma_array = new_array;

  *new_item = rma_array_elements++; /* (Post-increment as we want to return an array index, not how many elements there are) */

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_add_item: Successful, item count is now %d\n", rma_array_elements);
  #endif

  return NULL;
}

/*************************************************/
/* rma_remove_item()                             */
/*                                               */
/* Removes an rma_array_item from the array of   */
/* such structures.                              */
/*                                               */
/* Parameters: Index into the array of the item  */
/*             to remove.                        */
/*************************************************/

static _kernel_oserror * rma_remove_item(int item)
{
  rma_array_item * new_array;

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_remove_item: Called for item %d\n",item);
  #endif

  /* Limit check 'item' */

  if (item >= rma_array_elements)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid item number (%d, with %d elements) passed to rma_remove_item",
              item,
              rma_array_elements);

      return &erb;

    #endif

    return NULL;
  }

  /* Is there an associated RMA block to free? If so, */
  /* free it!                                         */

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_remove_item: \0212Freeing RMA block %p\0217\n",rma_array[item].rma_block);
  #endif

  if (rma_array[item].rma_block)
  {
    RetError(_swix(OS_Module,
                   _IN(0) | _IN(2),

                   7,
                   rma_array[item].rma_block));

    rma_array[item].rma_block = NULL;
  }

  /* Are we removing the only item? If so, just free the array */

  if (rma_array_elements == 1)
  {
    #ifdef TRACE
      if (tl & (1u<<12)) Printf("rma_remove_item: This is the last item, so freeing the array\n");
    #endif

    free(rma_array);

    rma_array          = NULL;
    rma_array_elements = 0;
  }
  else
  {
    /* Are we removing the last item? If not, must shuffle */
    /* the other elements down a bit.                      */

    if (item != rma_array_elements - 1)
    {
      #ifdef TRACE
        if (tl & (1u<<12)) Printf("rma_remove_item: This is not the head item, so shuffling higher items down\n");
      #endif

      memmove(&rma_array[item],
              &rma_array[item + 1],
              sizeof(rma_array_item) * (rma_array_elements - (item + 1)));
    }

    /* Now shrink the array */

    new_array = realloc(rma_array, sizeof(rma_array_item) * (rma_array_elements - 1));

    if (new_array) rma_array = new_array, rma_array_elements--;
  }

  /* Finished. */

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("rma_remove_item: Successful, item count is now %d\n", rma_array_elements);
  #endif

  return NULL;
}