/** @file
  Decode an Apple formatted partition table

  Copyright (c) 2009-2010, Oracle Corporation
**/


#include "Partition.h"

#define DPISTRLEN       32

#pragma pack(1)
typedef struct APPLE_PT_HEADER {
    UINT16  	sbSig;		/* must be BE 0x4552 */
    UINT16  	sbBlkSize;	/* block size of device */
    UINT32  	sbBlkCount;	/* number of blocks on device */
    UINT16  	sbDevType;	/* device type */
    UINT16  	sbDevId;	/* device id */
    UINT32  	sbData;		/* not used */
    UINT16  	sbDrvrCount;	/* driver descriptor count */
    UINT16  	sbMap[247];	/* descriptor map */
} APPLE_PT_HEADER;

typedef struct APPLE_PT_ENTRY  {
    UINT16       signature          ; /* must be BE 0x504D for new style PT */
    UINT16       reserved_1         ;
    UINT32       map_entries        ; /* how many PT entries are there */
    UINT32       pblock_start       ; /* first physical block */
    UINT32       pblocks            ; /* number of physical blocks */
    char         name[DPISTRLEN]    ; /* name of partition */
    char         type[DPISTRLEN]    ; /* type of partition */
    /* Some more data we don't really need */
} APPLE_PT_ENTRY;
#pragma pack()

/**
  Install child handles if the Handle supports Apple partition table format.

  @param[in]  This        Calling context.
  @param[in]  Handle      Parent Handle
  @param[in]  DiskIo      Parent DiskIo interface
  @param[in]  BlockIo     Parent BlockIo interface
  @param[in]  DevicePath  Parent Device Path


  @retval EFI_SUCCESS         Child handle(s) was added
  @retval EFI_MEDIA_CHANGED   Media changed Detected
  @retval other               no child handle was added

**/
EFI_STATUS
PartitionInstallAppleChildHandles (
  IN  EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN  EFI_HANDLE                   Handle,
  IN  EFI_DISK_IO_PROTOCOL         *DiskIo,
  IN  EFI_DISK_IO2_PROTOCOL        *DiskIo2,
  IN  EFI_BLOCK_IO_PROTOCOL        *BlockIo,
  IN  EFI_BLOCK_IO2_PROTOCOL       *BlockIo2,
  IN  EFI_DEVICE_PATH_PROTOCOL     *DevicePath
  )
{
  EFI_STATUS                Status;
  UINT32                    Lba;
  EFI_BLOCK_IO_MEDIA       *Media;
  VOID                     *Block;
  //UINTN                   MaxIndex;
  /** @todo: wrong, as this PT can be on both HDD or CD */
  CDROM_DEVICE_PATH         CdDev;
  //EFI_DEVICE_PATH_PROTOCOL  Dev;
  EFI_STATUS                Found;
  UINT32                    Partition;
  UINT32                    PartitionEntries;
  UINT32                    VolSpaceSize;
  UINT32                    SubBlockSize;
  UINT32                    BlkPerSec;
  UINT32                    MediaId;
  UINT32                    BlockSize;
  EFI_LBA                   LastBlock;
  EFI_DISK_IO2_TOKEN        DiskIo2Token;

  Found         = EFI_NOT_FOUND;
  VolSpaceSize  = 0;

  if (BlockIo2 != NULL)
  {
    Media         = BlockIo2->Media;
    BlockSize     = BlockIo2->Media->BlockSize;
    LastBlock     = BlockIo2->Media->LastBlock;
    MediaId       = BlockIo2->Media->MediaId;
  } else {
    Media         = BlockIo->Media;
    BlockSize     = BlockIo->Media->BlockSize;
    LastBlock     = BlockIo->Media->LastBlock;
    MediaId       = BlockIo->Media->MediaId;
  }

  Block = AllocatePool ((UINTN)BlockSize);

  if (Block == NULL) {
    return EFI_NOT_FOUND;
  }

  do {
      APPLE_PT_HEADER * Header;

      /* read PT header first */
      Lba = 0;

      if (DiskIo2 != NULL)
      {
        Status = DiskIo2->ReadDiskEx (
                       DiskIo2,
                       MediaId,
                       MultU64x32 (Lba, BlockSize),
                       &DiskIo2Token,
                       BlockSize,
                       Block
                       );
      } else {
        Status = DiskIo->ReadDisk (
                       DiskIo,
                       MediaId,
                       MultU64x32 (Lba, BlockSize),
                       BlockSize,
                       Block
                       );
      }
      if (EFI_ERROR (Status)) {
          Found = Status;
          break;
      }

      Header = (APPLE_PT_HEADER *)Block;
      if (SwapBytes16(Header->sbSig) != 0x4552) {
          break;
      }
      SubBlockSize = SwapBytes16(Header->sbBlkSize);
      BlkPerSec    = BlockSize / SubBlockSize;

      /* Fail if media block size isn't an exact multiple */
      if (BlockSize != SubBlockSize * BlkPerSec) {
          break;
      }

      /* Now iterate over PT entries and install child handles */
      PartitionEntries = 1;
      for (Partition = 1; Partition <= PartitionEntries; Partition++) {
          APPLE_PT_ENTRY * Entry;
          UINT32 StartLba;
          UINT32 SizeLbs;

          if (DiskIo2 != NULL)
          {
            Status = DiskIo2->ReadDiskEx (
                       DiskIo2,
                       MediaId,
                       MultU64x32 (Partition, SubBlockSize),
                       &DiskIo2Token,
                       SubBlockSize,
                       Block
                       );
          } else {
            Status = DiskIo->ReadDisk (
                       DiskIo,
                       MediaId,
                       MultU64x32 (Partition, SubBlockSize),
                       SubBlockSize,
                       Block
                       );
          }

          if (EFI_ERROR (Status)) {
              Status = EFI_NOT_FOUND;
              goto done; /* would break, but ... */
          }

          Entry = (APPLE_PT_ENTRY *)Block;

          if (SwapBytes16(Entry->signature) != 0x504D) {
              Print(L"Not a new PT entry: %x", Entry->signature);
              continue;
          }

          /* First partition contains partitions count */
          if (Partition == 1) {
             PartitionEntries  = SwapBytes32(Entry->map_entries);
          }

          StartLba = SwapBytes32(Entry->pblock_start);
          SizeLbs  = SwapBytes32(Entry->pblocks);

   /*       if (0 && CompareMem("Apple_HFS", Entry->type, 10) == 0)
              Print(L"HFS partition (%d of %d) at LBA 0x%x size=%dM\n",
                    Partition, PartitionEntries, StartLba,
                    (UINT32)(MultU64x32(SizeLbs, SubBlockSize) / (1024 * 1024)));
    */
      //
      // Create child device handle
      //

          ZeroMem (&CdDev, sizeof (CdDev));
          CdDev.Header.Type     = MEDIA_DEVICE_PATH;
          CdDev.Header.SubType  = MEDIA_CDROM_DP;
          SetDevicePathNodeLength (&CdDev.Header, sizeof (CdDev));

          CdDev.BootEntry = 0;
          /* Convert from partition to media blocks */
          CdDev.PartitionStart = StartLba / BlkPerSec;  /* start, LBA */
          CdDev.PartitionSize  = SizeLbs / BlkPerSec;   /* size,  LBs */

          Status = PartitionInstallChildHandle (
              This,
              Handle,
              DiskIo,
              DiskIo2,
              BlockIo,
              BlockIo2,
              DevicePath,
              (EFI_DEVICE_PATH_PROTOCOL *) &CdDev,
              CdDev.PartitionStart,
              CdDev.PartitionStart + CdDev.PartitionSize - 1,
              SubBlockSize,
              FALSE
                                                );

          if (!EFI_ERROR (Status)) {
              Found = EFI_SUCCESS;
          }
      }

  } while (0);

 done:
  FreePool (Block);

  return Found;
}
