/*++

Copyright (c) 2006, Intel Corporation
All rights reserved. This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

Module Name:

    EhciMem.c

Abstract:


Revision History
--*/

#include "Ehci.h"


EFI_STATUS
CreateMemoryBlock (
  IN  USB2_HC_DEV               *HcDev,
  OUT MEMORY_MANAGE_HEADER      **MemoryHeader,
  IN  UINTN                     MemoryBlockSizeInPages
  )
/*++

Routine Description:

  Use PciIo->AllocateBuffer to allocate common buffer for the memory block,
  and use PciIo->Map to map the common buffer for Bus Master Read/Write.

Arguments:

  HcDev                  - USB2_HC_DEV
  MemoryHeader           - MEMORY_MANAGE_HEADER to output
  MemoryBlockSizeInPages - MemoryBlockSizeInPages

Returns:

  EFI_SUCCESS           Success
  EFI_OUT_OF_RESOURCES  Fail for no resources
  EFI_UNSUPPORTED       Unsupported currently

--*/
{
  EFI_STATUS            Status;
  VOID                  *CommonBuffer;
  EFI_PHYSICAL_ADDRESS  MappedAddress;
  UINTN                 MemoryBlockSizeInBytes;
  VOID                  *Mapping;

  //
  // Allocate memory for MemoryHeader
  //
  *MemoryHeader = AllocateZeroPool (sizeof (MEMORY_MANAGE_HEADER));
  if (*MemoryHeader == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  (*MemoryHeader)->Next = NULL;

  //
  // set Memory block size
  //
  (*MemoryHeader)->MemoryBlockSizeInBytes = EFI_PAGES_TO_SIZE (MemoryBlockSizeInPages);

  //
  // each bit in Bit Array will manage 32 bytes memory in memory block
  //
  (*MemoryHeader)->BitArraySizeInBytes = ((*MemoryHeader)->MemoryBlockSizeInBytes / MEM_UNIT_SIZE) / 8;

  //
  // Allocate memory for BitArray
  //
  (*MemoryHeader)->BitArrayPtr = AllocateZeroPool ((*MemoryHeader)->BitArraySizeInBytes);
  if ((*MemoryHeader)->BitArrayPtr == NULL) {
    gBS->FreePool (*MemoryHeader);
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Memory Block uses MemoryBlockSizeInPages pages,
  // and it is allocated as common buffer use.
  //
  Status = HcDev->PciIo->AllocateBuffer (
                           HcDev->PciIo,
                           AllocateAnyPages,
                           EfiBootServicesData,
                           MemoryBlockSizeInPages,
                           &CommonBuffer,
                           0
                           );
  if (EFI_ERROR (Status)) {
    gBS->FreePool ((*MemoryHeader)->BitArrayPtr);
    gBS->FreePool (*MemoryHeader);
    return EFI_OUT_OF_RESOURCES;
  }

  MemoryBlockSizeInBytes = EFI_PAGES_TO_SIZE (MemoryBlockSizeInPages);
  Status = HcDev->PciIo->Map (
                           HcDev->PciIo,
                           EfiPciIoOperationBusMasterCommonBuffer,
                           CommonBuffer,
                           &MemoryBlockSizeInBytes,
                           &MappedAddress,
                           &Mapping
                           );
  //
  // If returned Mapped size is less than the size
  // we request,do not support.
  //
  if (EFI_ERROR (Status) || (MemoryBlockSizeInBytes != EFI_PAGES_TO_SIZE (MemoryBlockSizeInPages))) {
    HcDev->PciIo->FreeBuffer (HcDev->PciIo, MemoryBlockSizeInPages, CommonBuffer);
    gBS->FreePool ((*MemoryHeader)->BitArrayPtr);
    gBS->FreePool (*MemoryHeader);
    return EFI_UNSUPPORTED;
  }

  //
  // Data structure involved by host controller
  // should be restricted into the same 4G
  //
  if (HcDev->Is64BitCapable != 0) {
  	if (HcDev->High32BitAddr != GET_32B_TO_63B (MappedAddress)) {
	  HcDev->PciIo->Unmap (HcDev->PciIo, Mapping);
      HcDev->PciIo->FreeBuffer (HcDev->PciIo, MemoryBlockSizeInPages, CommonBuffer);
      gBS->FreePool ((*MemoryHeader)->BitArrayPtr);
      gBS->FreePool (*MemoryHeader);
      return EFI_UNSUPPORTED;
	}
  }

  //
  // Set Memory block initial address
  //
  (*MemoryHeader)->MemoryBlockPtr = (UINT8 *) ((UINTN) MappedAddress);
  (*MemoryHeader)->Mapping        = Mapping;

  ZeroMem (
    (*MemoryHeader)->MemoryBlockPtr,
    EFI_PAGES_TO_SIZE (MemoryBlockSizeInPages)
    );

  return EFI_SUCCESS;
}

EFI_STATUS
FreeMemoryHeader (
  IN USB2_HC_DEV               *HcDev,
  IN MEMORY_MANAGE_HEADER      *MemoryHeader
  )
/*++

Routine Description:

  Free Memory Header

Arguments:

  HcDev         - USB2_HC_DEV
  MemoryHeader  - MemoryHeader to be freed

Returns:

  EFI_SUCCESS            Success
  EFI_INVALID_PARAMETER  Parameter is error

--*/
{
  if ((MemoryHeader == NULL) || (HcDev == NULL)) {
    return EFI_INVALID_PARAMETER;
  }
  //
  // unmap the common buffer used by the memory block
  //
  HcDev->PciIo->Unmap (HcDev->PciIo, MemoryHeader->Mapping);

  //
  // free common buffer
  //
  HcDev->PciIo->FreeBuffer (
                  HcDev->PciIo,
                  EFI_SIZE_TO_PAGES (MemoryHeader->MemoryBlockSizeInBytes),
                  MemoryHeader->MemoryBlockPtr
                  );
  //
  // free bit array
  //
  gBS->FreePool (MemoryHeader->BitArrayPtr);
  //
  // free memory header
  //
  gBS->FreePool (MemoryHeader);

  return EFI_SUCCESS;
}

EFI_STATUS
EhciAllocatePool (
  IN  USB2_HC_DEV     *HcDev,
  OUT UINT8           **Pool,
  IN  UINTN           AllocSize
  )
/*++

Routine Description:

  Ehci Allocate Pool

Arguments:

  HcDev     - USB2_HC_DEV
  Pool      - Place to store pointer to the memory buffer
  AllocSize - Alloc Size

Returns:

  EFI_SUCCESS        Success
  EFI_DEVICE_ERROR   Fail

--*/
{
  MEMORY_MANAGE_HEADER  *MemoryHeader;
  MEMORY_MANAGE_HEADER  *TempHeaderPtr;
  MEMORY_MANAGE_HEADER  *NewMemoryHeader;
  UINTN                 RealAllocSize;
  UINTN                 MemoryBlockSizeInPages;
  EFI_STATUS            Status;
  EFI_TPL               OldTpl;

  *Pool         = NULL;

  MemoryHeader  = HcDev->MemoryHeader;
  ASSERT (MemoryHeader != NULL);

  OldTpl = gBS->RaiseTPL (EFI_TPL_NOTIFY + 1);

  //
  // allocate unit is 32 bytes (align on 32 byte)
  //
  if (AllocSize & (MEM_UNIT_SIZE - 1)) {
    RealAllocSize = (AllocSize / MEM_UNIT_SIZE + 1) * MEM_UNIT_SIZE;
  } else {
    RealAllocSize = AllocSize;
  }

  //
  // There may be linked MemoryHeaders.
  // To allocate a free pool in Memory blocks,
  // must search in the MemoryHeader link list
  // until enough free pool is found.
  //
  Status = EFI_NOT_FOUND;
  for (TempHeaderPtr = MemoryHeader; TempHeaderPtr != NULL; TempHeaderPtr = TempHeaderPtr->Next) {

    Status = AllocMemInMemoryBlock (
              TempHeaderPtr,
              (VOID **) Pool,
              RealAllocSize / MEM_UNIT_SIZE
              );
    if (!EFI_ERROR (Status)) {
       break;
    }
  }

  gBS->RestoreTPL (OldTpl);

  if (!EFI_ERROR (Status)) {
     ZeroMem (*Pool, AllocSize);
     return EFI_SUCCESS;
  }


  //
  // There is no enough memory,
  // Create a new Memory Block
  //

  //
  // if pool size is larger than NORMAL_MEMORY_BLOCK_UNIT_IN_PAGES,
  // just allocate a large enough memory block.
  //
  if (RealAllocSize > EFI_PAGES_TO_SIZE (NORMAL_MEMORY_BLOCK_UNIT_IN_PAGES)) {
    MemoryBlockSizeInPages = EFI_SIZE_TO_PAGES (RealAllocSize) + 1;
  } else {
    MemoryBlockSizeInPages = NORMAL_MEMORY_BLOCK_UNIT_IN_PAGES;
  }

  Status = CreateMemoryBlock (HcDev, &NewMemoryHeader, MemoryBlockSizeInPages);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  OldTpl = gBS->RaiseTPL (EFI_TPL_NOTIFY + 1);

  	
  //
  // Link the new Memory Block to the Memory Header list
  //
  InsertMemoryHeaderToList (MemoryHeader, NewMemoryHeader);

  Status = AllocMemInMemoryBlock (
             NewMemoryHeader,
             (VOID **) Pool,
             RealAllocSize / MEM_UNIT_SIZE
             );

  gBS->RestoreTPL (OldTpl);

  if (!EFI_ERROR (Status)) {
    ZeroMem (*Pool, AllocSize);
  }

  return Status;
}

VOID
EhciFreePool (
  IN USB2_HC_DEV     *HcDev,
  IN UINT8           *Pool,
  IN UINTN           AllocSize
  )
/*++

Routine Description:

  Uhci Free Pool

Arguments:

  HcDev     - USB_HC_DEV
  Pool      - Pool to free
  AllocSize - Pool size

Returns:

  VOID

--*/
{
  MEMORY_MANAGE_HEADER  *MemoryHeader;
  MEMORY_MANAGE_HEADER  *TempHeaderPtr;
  UINTN                 StartBytePos;
  UINTN                 Index;
  UINT8                 StartBitPos;
  UINT8                 Index2;
  UINTN                 Count;
  UINTN                 RealAllocSize;
  EFI_TPL               OldTpl;

  OldTpl        = gBS->RaiseTPL (EFI_TPL_NOTIFY + 1);

  MemoryHeader  = HcDev->MemoryHeader;

  //
  // allocate unit is 32 byte (align on 32 byte)
  //
  if (AllocSize & (MEM_UNIT_SIZE - 1)) {
    RealAllocSize = (AllocSize / MEM_UNIT_SIZE + 1) * MEM_UNIT_SIZE;
  } else {
    RealAllocSize = AllocSize;
  }

  //
  // scan the memory header linked list for
  // the asigned memory to free.
  //
  for (TempHeaderPtr = MemoryHeader; TempHeaderPtr != NULL; TempHeaderPtr = TempHeaderPtr->Next) {

    if ((Pool >= TempHeaderPtr->MemoryBlockPtr) &&
        ((Pool + RealAllocSize) <= (TempHeaderPtr->MemoryBlockPtr + TempHeaderPtr->MemoryBlockSizeInBytes))
        ) {
      //
      // Pool is in the Memory Block area,
      // find the start byte and bit in the bit array
      //
      StartBytePos  = ((Pool - TempHeaderPtr->MemoryBlockPtr) / MEM_UNIT_SIZE) / 8;
      StartBitPos   = (UINT8) (((Pool - TempHeaderPtr->MemoryBlockPtr) / MEM_UNIT_SIZE) & 0x7);

      //
      // reset associated bits in bit arry
      //
      for (Index = StartBytePos, Index2 = StartBitPos, Count = 0; Count < (RealAllocSize / MEM_UNIT_SIZE); Count++) {
        ASSERT ((TempHeaderPtr->BitArrayPtr[Index] & bit (Index2) )== bit (Index2));

        TempHeaderPtr->BitArrayPtr[Index] = (UINT8) (TempHeaderPtr->BitArrayPtr[Index] ^ (bit (Index2)));
        Index2++;
        if (Index2 == 8) {
          Index += 1;
          Index2 = 0;
        }
      }
      //
      // break the loop
      //
      break;
    }
  }

  //
  // Release emptied memory blocks (only if the memory block is not
  // the first one in the memory header list
  //
  for (TempHeaderPtr = MemoryHeader->Next; TempHeaderPtr != NULL;) {

    ASSERT (MemoryHeader->Next != NULL);

    if (IsMemoryBlockEmptied (TempHeaderPtr)) {

      DelinkMemoryBlock (MemoryHeader, TempHeaderPtr);
      //
      // when the TempHeaderPtr is freed in FreeMemoryHeader(),
      // the TempHeaderPtr is pointing to nonsense content.
      //
      gBS->RestoreTPL (OldTpl);
      FreeMemoryHeader (HcDev, TempHeaderPtr);
      OldTpl = gBS->RaiseTPL (EFI_TPL_NOTIFY + 1);
      //
      // reset the TempHeaderPtr, continue search for
      // another empty memory block.
      //
      TempHeaderPtr = MemoryHeader->Next;
      continue;
    }

    TempHeaderPtr = TempHeaderPtr->Next;
  }

  gBS->RestoreTPL (OldTpl);
}

VOID
InsertMemoryHeaderToList (
  IN MEMORY_MANAGE_HEADER     *MemoryHeader,
  IN MEMORY_MANAGE_HEADER     *NewMemoryHeader
  )
/*++

Routine Description:

  Insert Memory Header To List

Arguments:

  MemoryHeader    - MEMORY_MANAGE_HEADER
  NewMemoryHeader - MEMORY_MANAGE_HEADER

Returns:

  VOID

--*/
{
  MEMORY_MANAGE_HEADER  *TempHeaderPtr;

  for (TempHeaderPtr = MemoryHeader; TempHeaderPtr != NULL; TempHeaderPtr = TempHeaderPtr->Next) {
    if (TempHeaderPtr->Next == NULL) {
      TempHeaderPtr->Next = NewMemoryHeader;
      break;
    }
  }
}

EFI_STATUS
AllocMemInMemoryBlock (
  IN  MEMORY_MANAGE_HEADER     *MemoryHeader,
  OUT VOID                     **Pool,
  IN  UINTN                    NumberOfMemoryUnit
  )
/*++

Routine Description:

  Alloc Memory In MemoryBlock

Arguments:

  MemoryHeader        - MEMORY_MANAGE_HEADER
  Pool                - Place to store pointer to memory
  NumberOfMemoryUnit  - Number Of Memory Unit

Returns:

  EFI_SUCCESS    Success
  EFI_NOT_FOUND  Can't find the free memory

--*/
{
  UINTN TempBytePos;
  UINTN FoundBytePos;
  UINT8 Index;
  UINT8 FoundBitPos;
  UINT8 ByteValue;
  UINT8 BitValue;
  UINTN NumberOfZeros;
  UINTN Count;

  FoundBytePos  = 0;
  FoundBitPos   = 0;
  ByteValue     = MemoryHeader->BitArrayPtr[0];
  NumberOfZeros = 0;
  Index         = 0;

  for (TempBytePos = 0; TempBytePos < MemoryHeader->BitArraySizeInBytes;) {
  	
    //
    // Pop out BitValue from a byte in TempBytePos.
    //
    BitValue = (UINT8) (ByteValue & 0x1);
	
    //
    // right shift the byte
    //
    ByteValue = (UINT8) (ByteValue >> 1);

    if (BitValue == 0) {
      //
      // Found a free bit, the NumberOfZeros only record the number
      // of those consecutive zeros
      //
      NumberOfZeros++;
      //
      // Found enough consecutive free space, break the loop
      //
      if (NumberOfZeros >= NumberOfMemoryUnit) {
        break;
      }
    } else {
      //
      // Encountering a '1', meant the bit is ocupied.
      //
      if (NumberOfZeros >= NumberOfMemoryUnit) {
        //
        // Found enough consecutive free space,break the loop
        //
        break;
      } else {
        //
        // the NumberOfZeros only record the number of those consecutive zeros,
        // so reset the NumberOfZeros to 0 when encountering '1' before finding
        // enough consecutive '0's
        //
        NumberOfZeros = 0;
        //
        // reset the (FoundBytePos,FoundBitPos) to the position of '1'
        //
        FoundBytePos  = TempBytePos;
        FoundBitPos   = Index;
      }
    }
	
    //
    // step forward a bit
    //
    Index++;
    if (Index == 8) {
      //
      // step forward a byte, getting the byte value,
      // and reset the bit pos.
      //
      TempBytePos += 1;
      ByteValue = MemoryHeader->BitArrayPtr[TempBytePos];
      Index     = 0;
    }
  }

  if (NumberOfZeros < NumberOfMemoryUnit) {
    return EFI_NOT_FOUND;
  }

  //
  // Found enough free space.
  //

  //
  // The values recorded in (FoundBytePos,FoundBitPos) have two conditions:
  //  1)(FoundBytePos,FoundBitPos) record the position
  //    of the last '1' before the consecutive '0's, it must
  //    be adjusted to the start position of the consecutive '0's.
  //  2)the start address of the consecutive '0's is just the start of
  //    the bitarray. so no need to adjust the values of
  //    (FoundBytePos,FoundBitPos).
  //
  if ((MemoryHeader->BitArrayPtr[FoundBytePos] & bit (FoundBitPos)) != 0) {
    FoundBitPos += 1;
  }

  //
  // Have the (FoundBytePos,FoundBitPos) make sense.
  //
  if (FoundBitPos > 7) {
    FoundBytePos += 1;
    FoundBitPos -= 8;
  }

  //
  // Set the memory as allocated
  //
  for (TempBytePos = FoundBytePos, Index = FoundBitPos, Count = 0; Count < NumberOfMemoryUnit; Count++) {

    ASSERT ((MemoryHeader->BitArrayPtr[TempBytePos] & bit (Index) )== 0);
    MemoryHeader->BitArrayPtr[TempBytePos] = (UINT8) (MemoryHeader->BitArrayPtr[TempBytePos] | bit (Index));
    Index++;
    if (Index == 8) {
      TempBytePos += 1;
      Index = 0;
    }
  }

  *Pool = MemoryHeader->MemoryBlockPtr + (FoundBytePos * 8 + FoundBitPos) * MEM_UNIT_SIZE;

  return EFI_SUCCESS;
}

BOOLEAN
IsMemoryBlockEmptied (
  IN MEMORY_MANAGE_HEADER     *MemoryHeaderPtr
  )
/*++

Routine Description:

  Is Memory Block Emptied

Arguments:

  MemoryHeaderPtr - MEMORY_MANAGE_HEADER

Returns:

  TRUE    Empty
  FALSE   Not Empty

--*/
{
  UINTN Index;

  for (Index = 0; Index < MemoryHeaderPtr->BitArraySizeInBytes; Index++) {
    if (MemoryHeaderPtr->BitArrayPtr[Index] != 0) {
      return FALSE;
    }
  }

  return TRUE;
}

VOID
DelinkMemoryBlock (
  IN MEMORY_MANAGE_HEADER     *FirstMemoryHeader,
  IN MEMORY_MANAGE_HEADER     *NeedFreeMemoryHeader
  )
/*++

Routine Description:

  Delink Memory Block

Arguments:

  FirstMemoryHeader     - MEMORY_MANAGE_HEADER
  NeedFreeMemoryHeader  - MEMORY_MANAGE_HEADER

Returns:

  VOID

--*/
{
  MEMORY_MANAGE_HEADER  *TempHeaderPtr;

  if ((FirstMemoryHeader == NULL) || (NeedFreeMemoryHeader == NULL)) {
    return ;
  }

  for (TempHeaderPtr = FirstMemoryHeader; TempHeaderPtr != NULL; TempHeaderPtr = TempHeaderPtr->Next) {

    if (TempHeaderPtr->Next == NeedFreeMemoryHeader) {
      //
      // Link the before and after
      //
      TempHeaderPtr->Next = NeedFreeMemoryHeader->Next;
      NeedFreeMemoryHeader->Next = NULL;
      break;
    }
  }
}

EFI_STATUS
InitialMemoryManagement (
  IN USB2_HC_DEV     *HcDev
  )
/*++

Routine Description:

  Initialize Memory Management

Arguments:

  HcDev  - USB2_HC_DEV

Returns:

  EFI_SUCCESS        Success
  EFI_DEVICE_ERROR   Fail

--*/
{
  EFI_STATUS            Status;
  MEMORY_MANAGE_HEADER  *MemoryHeader;
  UINTN                 MemPages;

  MemPages  = NORMAL_MEMORY_BLOCK_UNIT_IN_PAGES;
  Status    = CreateMemoryBlock (HcDev, &MemoryHeader, MemPages);
  if (EFI_ERROR (Status)) {
    Status = EFI_OUT_OF_RESOURCES;
    goto exit;
  }

  HcDev->MemoryHeader = MemoryHeader;

exit:
  return Status;
}

EFI_STATUS
DeinitialMemoryManagement (
  IN USB2_HC_DEV     *HcDev
  )
/*++

Routine Description:

  Deinitialize Memory Management

Arguments:

  HcDev   - USB2_HC_DEV

Returns:

  EFI_SUCCESS        Success
  EFI_DEVICE_ERROR   Fail

--*/
{
  MEMORY_MANAGE_HEADER  *TempHeaderPtr;

  for (TempHeaderPtr = HcDev->MemoryHeader->Next; TempHeaderPtr != NULL;) {

    DelinkMemoryBlock (HcDev->MemoryHeader, TempHeaderPtr);
    //
    // when the TempHeaderPtr is freed in FreeMemoryHeader(),
    // the TempHeaderPtr is pointing to nonsense content.
    //
    FreeMemoryHeader (HcDev, TempHeaderPtr);
    //
    // reset the TempHeaderPtr,continue free another memory block.
    //
    TempHeaderPtr = HcDev->MemoryHeader->Next;
  }

  FreeMemoryHeader (HcDev, HcDev->MemoryHeader);

  return EFI_SUCCESS;
}
