/*
 *  Copyright (c) by Jaroslav Kysela (Perex soft)
 *  GUS's memory allocation routines / bottom layer
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "driver.h"
#include "gus.h"
#include "info.h"

#ifdef SNDCFG_DEBUG
static void snd_gf1_mem_info_read( snd_info_buffer_t *buffer, void *private_data );
#endif

snd_gf1_mem_block_t *snd_gf1_mem_xalloc( snd_gf1_mem_t *alloc, snd_gf1_mem_block_t *block )
{
  snd_gf1_mem_block_t *pblock, *nblock;
  
  snd_mutex_down( alloc, memory );
  nblock = (snd_gf1_mem_block_t *)snd_malloc( sizeof( snd_gf1_mem_block_t ) );
  if ( !nblock ) {
    snd_mutex_up( alloc, memory );
    return NULL;
  }
  memcpy( nblock, block, sizeof( snd_gf1_mem_block_t ) );
  pblock = alloc -> first;
  while ( pblock )
    {
      if ( pblock -> ptr > nblock -> ptr ) 
        {
          nblock -> prev = pblock -> prev;
          nblock -> next = pblock;
          pblock -> prev = nblock;
          if ( pblock == alloc -> first )
            alloc -> first = nblock;
           else
            nblock -> prev -> next = nblock;
          snd_mutex_up( alloc, memory );
          return 0;
        }
      pblock = pblock -> next;
    }
  nblock -> next = NULL;
  if ( !alloc -> last )
    {
      nblock -> prev = NULL;
      alloc -> first = alloc -> last = nblock;
    }
   else
    {
      nblock -> prev = alloc -> last;
      alloc -> last -> next = nblock;
      alloc -> last = nblock;
    }
  snd_mutex_up( alloc, memory );
  return nblock;
}

int snd_gf1_mem_xfree( snd_gf1_mem_t *alloc, snd_gf1_mem_block_t *block )
{
  snd_mutex_down( alloc, memory );
  if ( block -> share )			/* ok.. shared block */
    {
      block -> share--;
      snd_mutex_up( alloc, memory );
      return 0;
    }
  if ( alloc -> first == block )
    { 
      alloc -> first = block -> next;
      if ( block -> next ) block -> next -> prev = NULL;
    }
   else
    {
      block -> prev -> next = block -> next;
      if ( block -> next ) block -> next -> prev = block -> prev;
    }
  if ( alloc -> last == block )
    {
      alloc -> last = block -> prev;
      if ( block -> prev ) block -> prev -> next = NULL;
    }
   else
    {
      block -> next -> prev = block -> prev;
      if ( block -> prev ) block -> prev -> next = block -> next;
    } 
  if ( block -> owner != SND_GF1_MEM_OWNER_WAVE )
    snd_free_str( block -> data.name );
  snd_free( block, sizeof( snd_gf1_mem_block_t ) );
  snd_mutex_up( alloc, memory );
  return 0;
}

snd_gf1_mem_block_t *snd_gf1_mem_look( snd_gf1_mem_t *alloc, unsigned int address )
{
  snd_gf1_mem_block_t *block;
  
  snd_mutex_down( alloc, memory );
  for ( block = alloc -> first; block; block = block -> next )
    if ( block -> ptr == address ) {
      snd_mutex_up( alloc, memory );
      return block;
    }
  snd_mutex_up( alloc, memory );
  return NULL;
}

snd_gf1_mem_block_t *snd_gf1_mem_share( snd_gf1_mem_t *alloc, unsigned int share_id1, unsigned int share_id2 )
{
  snd_gf1_mem_block_t *block;
  
  if ( !share_id1 && !share_id2 ) return NULL;
  snd_mutex_down( alloc, memory );
  for ( block = alloc -> first; block; block = block -> next )
    if ( block -> share_id1 == share_id1 && block -> share_id2 == share_id2 ) {
      snd_mutex_up( alloc, memory );
      return block;
    }
  snd_mutex_up( alloc, memory );
  return NULL;
}

static int snd_gf1_mem_find( snd_gf1_mem_t *alloc, snd_gf1_mem_block_t *block, unsigned int size, int w_16, int align )
{
  snd_gf1_bank_info_t *info = w_16 ? alloc -> banks_16 : alloc -> banks_8;
  unsigned int idx, boundary;
  int size1;
  snd_gf1_mem_block_t *pblock;
  unsigned int ptr1, ptr2;
  
  align--;
  block -> flags = w_16 ? SND_GF1_MEM_BLOCK_16BIT : 0;
  block -> owner = SND_GF1_MEM_OWNER_DRIVER;
  block -> lock = 0;
  block -> share = block -> share_id1 = block -> share_id2 = 0;
  block -> data.name = NULL;
  block -> prev = block -> next = NULL;
  snd_mutex_down( alloc, memory );
  for ( pblock = alloc -> first, idx = 0; pblock; pblock = pblock -> next )
    {
      while ( pblock -> ptr >= ( boundary = info[ idx ].address + info[ idx ].size ) ) idx++;
      while ( pblock -> ptr + pblock -> size >= ( boundary = info[ idx ].address + info[ idx ].size ) ) idx++;
      ptr2 = boundary;
      if ( pblock -> next )
        {
          if ( pblock -> ptr + pblock -> size == pblock -> next -> ptr ) continue;
          if ( pblock -> next -> ptr < boundary )
            ptr2 = pblock -> next -> ptr;
        }
      ptr1 = ( pblock -> ptr + pblock -> size + align ) & ~align;
      if ( ptr1 >= ptr2 ) continue;
      size1 = ptr2 - ptr1;
      if ( size <= size1 )
        {
          block -> ptr = ptr1;
          block -> size = size;
          snd_mutex_up( alloc, memory );
          return 0;
        }
    }
  while ( ++idx < 4 )
    if ( size <= info[ idx ].size )
      {
        block -> ptr = info[ idx ].address;	/* I assume that bank address is already aligned.. */
        block -> size = size;
        snd_mutex_up( alloc, memory );
        return 0;
      }
  snd_mutex_up( alloc, memory );
  return -ENOMEM;
}

snd_gf1_mem_block_t *snd_gf1_mem_alloc( snd_gf1_mem_t *alloc, char *name, int size, int w_16, int align )
{
  snd_gf1_mem_block_t block, *nblock;
  
  snd_mutex_down( alloc, memory_find );
  if ( snd_gf1_mem_find( alloc, &block, size, w_16, align ) < 0 ) {
    snd_mutex_up( alloc, memory_find );
    return NULL;
  }
  block.data.name = snd_malloc_strdup( name );
  nblock = snd_gf1_mem_xalloc( alloc, &block );
  snd_mutex_up( alloc, memory_find );
  return nblock;
}

int snd_gf1_mem_free( snd_gf1_mem_t *alloc, unsigned int address )
{
  snd_gf1_mem_block_t *block;

  if ( ( block = snd_gf1_mem_look( alloc, address ) ) != NULL )
    return snd_gf1_mem_xfree( alloc, block );
  return -1;
}

int snd_gf1_mem_init( snd_gus_card_t *gus )
{
  snd_gf1_mem_t *alloc;
  snd_gf1_mem_block_t block;
#ifdef SNDCFG_DEBUG
  snd_info_entry_t *entry;
#endif

  alloc = &gus -> gf1.mem_alloc;
  snd_mutex_prepare( alloc, memory );
  snd_mutex_prepare( alloc, memory_find );
  alloc -> first = alloc -> last = NULL;
  if ( !gus -> gf1.memory ) return 0;
  
  memset( &block, 0, sizeof( block ) );
  block.flags = 0;
  block.owner = SND_GF1_MEM_OWNER_DRIVER;
  block.lock = SND_GF1_MEM_OWNER_DRIVER;
  block.share = block.share_id1 = block.share_id2 = 0;
  snd_mutex_down( alloc, memory_find );
  if ( gus -> gf1.enh_mode ) {
    block.ptr = 0;
    block.size = 1024;
    block.data.name = snd_malloc_strdup( "InterWave LFOs" );
    if ( snd_gf1_mem_xalloc( alloc, &block ) == NULL ) {
      snd_mutex_up( alloc, memory_find );
      return -ENOMEM;
    }
  }
  block.ptr = gus -> gf1.default_voice_address;
  block.size = 4;
  block.data.name = snd_malloc_strdup( "Voice default (NULL's)" );
  if ( snd_gf1_mem_xalloc( alloc, &block ) == NULL ) { 
    snd_mutex_up( alloc, memory_find );
    return -ENOMEM;
  }
  snd_mutex_up( alloc, memory_find );
#ifdef SNDCFG_DEBUG
  alloc -> info_entry = NULL;
  entry = snd_info_create_entry( gus -> card, "gusmem" );
  if ( !entry ) return -ENOMEM;
  entry -> t.text.read_size = 256 * 1024;
  entry -> t.text.read = snd_gf1_mem_info_read;
  entry -> private_data = gus;
  if ( snd_info_register( entry ) < 0 ) {
    snd_info_free_entry( entry );
  } else {
    alloc -> info_entry = entry;
  }
#endif
  return 0;
}

int snd_gf1_mem_done( snd_gus_card_t *gus )
{
  snd_gf1_mem_t *alloc;
  snd_gf1_mem_block_t *block;

  alloc = &gus -> gf1.mem_alloc;
  for ( block = alloc -> first; block; block = block -> next )
    snd_gf1_mem_xfree( alloc, block );
#ifdef SNDCFG_DEBUG
  if ( alloc -> info_entry )
    snd_info_unregister( alloc -> info_entry );
#endif
  return 0;
}

#ifdef SNDCFG_DEBUG
static void snd_gf1_mem_info_read( snd_info_buffer_t *buffer, void *private_data )
{
  snd_gus_card_t *gus;
  snd_gf1_mem_t *alloc;
  snd_gf1_mem_block_t *block;
  unsigned int total, used;
  int i;

  gus = (snd_gus_card_t *)private_data;
  alloc = &gus -> gf1.mem_alloc;
  snd_mutex_down( alloc, memory );
  snd_iprintf( buffer, "8-bit banks       : \n    " );
  for ( i = 0; i < 4; i++ )
    snd_iprintf( buffer, "0x%06x (%04ik)%s", alloc -> banks_8[ i ].address, alloc -> banks_8[ i ].size >> 10, i + 1 < 4 ? "," : "" );
  snd_iprintf( buffer, "\n"
                       "16-bit banks      : \n    " );
  for ( i = total = 0; i < 4; i++ ) {
    snd_iprintf( buffer, "0x%06x (%04ik)%s", alloc -> banks_16[ i ].address, alloc -> banks_16[ i ].size >> 10, i + 1 < 4 ? "," : "" );
    total += alloc -> banks_16[ i ].size;
  }
  snd_iprintf( buffer, "\n" );  
  used = 0;
  for ( block = alloc -> first, i = 0; block; block = block -> next, i++ ) {
    used += block -> size;
    snd_iprintf( buffer, "Block %i at 0x%lx onboard 0x%x size %i (0x%x):\n", i, (long)block, block -> ptr, block -> size, block -> size );
    if ( block -> share || block -> share_id1 || block -> share_id2 )
      snd_iprintf( buffer, "  Share           : %i [id1 0x%x] [id2 0x%x]\n", block -> share, block -> share_id1, block -> share_id2 );
    snd_iprintf( buffer, "  Flags           :%s\n",
      			block -> flags & SND_GF1_MEM_BLOCK_16BIT ? " 16-bit" : "" );
    snd_iprintf( buffer, "  Owner           : " );
    switch ( block -> owner ) {
      case SND_GF1_MEM_OWNER_DRIVER:
        snd_iprintf( buffer, "driver - %s\n", block -> data.name );
        break;
      case SND_GF1_MEM_OWNER_WAVE:
#if 0
        snd_iprintf( buffer, "wave at 0x%lx\n", (long)block -> data.wave );
#else
        snd_iprintf( buffer, "wave at 0x%lx\n", (long)block -> data.name );
#endif
        break;
      case SND_GF1_MEM_OWNER_USER:
        snd_iprintf( buffer, "user - '%s'\n", block -> data.name );
        break;
      case SND_GF1_MEM_OWNER_SERVER:
        snd_iprintf( buffer, "server - '%s'\n", block -> data.name );
        break;
      default:
        snd_iprintf( buffer, "uknown\n" );
    }
    if ( block -> lock ) {
      snd_iprintf( buffer, "  Locked by group : " );
      switch ( block -> lock ) {
        case SND_GF1_MEM_OWNER_DRIVER: snd_iprintf( buffer, "driver\n" ); break;
        case SND_GF1_MEM_OWNER_WAVE: snd_iprintf( buffer, "wave\n" ); break;
        case SND_GF1_MEM_OWNER_USER: snd_iprintf( buffer, "user\n" ); break;
        case SND_GF1_MEM_OWNER_SERVER: snd_iprintf( buffer, "daemon\n" ); break;
        default:
          snd_iprintf( buffer, "unknown\n" );
      }
    }
  }
  snd_iprintf( buffer, "  Total: memory = %i, used = %i, free = %i\n",
  			total, used, total - used );
  snd_mutex_up( alloc, memory );
#if 0
  ultra_iprintf( buffer, "  Verify: free = %i, max 8-bit block = %i, max 16-bit block = %i\n",
  			ultra_memory_free_size( card, &card -> gf1.mem_alloc ),
  			ultra_memory_free_block( card, &card -> gf1.mem_alloc, 0 ),
  			ultra_memory_free_block( card, &card -> gf1.mem_alloc, 1 ) );
#endif
}
#endif
