/* $Id: Apple.c $ */
/** @file
 * Apple.c
 */

/*
 * Copyright (C) 2012 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */

#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()

static UINT16
be16_to_cpu(UINT16 x)
{
    return SwapBytes16(x);
}

static UINT32
be32_to_cpu(UINT32 x)
{
    return SwapBytes32(x);
}


/**
  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_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;

  VBoxLogFlowFuncMarkDP(DevicePath);
  Found         = EFI_NOT_FOUND;
  Media         = BlockIo->Media;
  VolSpaceSize  = 0;

  Block = AllocatePool ((UINTN) Media->BlockSize);

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

  do {
      APPLE_PT_HEADER * Header;

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

      Status = DiskIo->ReadDisk (
                       DiskIo,
                       Media->MediaId,
                       MultU64x32 (Lba, Media->BlockSize),
                       Media->BlockSize,
                       Block
                       );
      if (EFI_ERROR (Status))
      {
          Found = Status;
          break;
      }

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

      /* Fail if media block size isn't an exact multiple */
      if (Media->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;

          Status = DiskIo->ReadDisk (
                       DiskIo,
                       Media->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 (be16_to_cpu(Entry->signature) != 0x504D)
          {
              Print(L"Not a new PT entry: %x", Entry->signature);
              continue;
          }

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

          StartLba = be32_to_cpu(Entry->pblock_start);
          SizeLbs  = be32_to_cpu(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)));

          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,
              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;
}
