/*  This file is part of the program psim.
    
    Copyright (C) 1994-1997, Andrew Cagney <cagney@highland.com.au>
    
    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 3 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, see <http://www.gnu.org/licenses/>.
    
    */


#ifndef _PK_DISKLABEL_C_
#define _PK_DISKLABEL_C_

#ifndef STATIC_INLINE_PK_DISKLABEL
#define STATIC_INLINE_PK_DISKLABEL STATIC_INLINE
#endif

#include "device_table.h"

#include "pk.h"

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif



/* PACKAGE

   disk-label - all knowing disk I/O package

   DESCRIPTION

   The disk-label package provides a generic interface to disk
   devices.  It uses the arguments specified when an instance is being
   created to determine if the raw disk, a partition, or a file within
   a partition should be opened.

   An instance create call to disk-label could result, for instance,
   in the opening of a DOS file system contained within a dos
   partition contained within a physical disk.

   */

/* taken from bfd/ppcboot.c by Michael Meissner */

/* PPCbug location structure */
typedef struct ppcboot_location {
  unsigned8 ind;
  unsigned8 head;
  unsigned8 sector;
  unsigned8 cylinder;
} ppcboot_location_t;

/* PPCbug partition table layout */
typedef struct ppcboot_partition {
  ppcboot_location_t partition_begin;	/* partition begin */
  ppcboot_location_t partition_end;	/* partition end */
  unsigned8 sector_begin[4];		/* 32-bit start RBA (zero-based), little endian */
  unsigned8 sector_length[4];		/* 32-bit RBA count (one-based), little endian */
} ppcboot_partition_t;

#if 0
/* PPCbug boot layout.  */
typedef struct ppcboot_hdr {
  unsigned8		pc_compatibility[446];	/* x86 instruction field */
  ppcboot_partition_t	partition[4];		/* partition information */
  unsigned8		signature[2];		/* 0x55 and 0xaa */
  unsigned8		entry_offset[4];	/* entry point offset, little endian */
  unsigned8		length[4];		/* load image length, little endian */
  unsigned8		flags;			/* flag field */
  unsigned8		os_id;			/* OS_ID */
  char			partition_name[32];	/* partition name */
  unsigned8		reserved1[470];		/* reserved */
} ppcboot_hdr_t;
#endif


typedef struct _disklabel {
  device_instance *parent;
  device_instance *raw_disk;
  unsigned_word pos;
  unsigned_word sector_begin;
  unsigned_word sector_length;
} disklabel;


static unsigned_word
sector2uw(unsigned8 s[4])
{
  return ((s[3] << 24)
	  + (s[2] << 16)
	  + (s[1] << 8)
	  + (s[0] << 0));
}


static void
disklabel_delete(device_instance *instance)
{
  disklabel *label = device_instance_data(instance);
  device_instance_delete(label->raw_disk);
  free(label);
}


static int
disklabel_read(device_instance *instance,
	       void *buf,
	       unsigned_word len)
{
  disklabel *label = device_instance_data(instance);
  int nr_read;
  if (label->pos + len > label->sector_length)
    len = label->sector_length - label->pos;
  if (device_instance_seek(label->raw_disk, 0,
			   label->sector_begin + label->pos) < 0)
    return -1;
  nr_read = device_instance_read(label->raw_disk, buf, len);
  if (nr_read > 0)
    label->pos += nr_read;
  return nr_read;
}

static int
disklabel_write(device_instance *instance,
		const void *buf,
		unsigned_word len)
{
  disklabel *label = device_instance_data(instance);
  int nr_written;
  if (label->pos + len > label->sector_length)
    len = label->sector_length - label->pos;
  if (device_instance_seek(label->raw_disk, 0,
			   label->sector_begin + label->pos) < 0)
    return -1;
  nr_written = device_instance_write(label->raw_disk, buf, len);
  if (nr_written > 0)
    label->pos += nr_written;
  return nr_written;
}

static int
disklabel_seek(device_instance *instance,
	       unsigned_word pos_hi,
	       unsigned_word pos_lo)
{
  disklabel *label = device_instance_data(instance);
  if (pos_lo >= label->sector_length || pos_hi != 0)
    return -1;
  label->pos = pos_lo;
  return 0;
}


static const device_instance_callbacks package_disklabel_callbacks = {
  disklabel_delete,
  disklabel_read,
  disklabel_write,
  disklabel_seek,
};

/* Reconize different types of boot block */

static int
block0_is_bpb(const unsigned8 block[])
{
  const char ebdic_ibma[] = { 0xc9, 0xc2, 0xd4, 0xc1 };
  /* ref PowerPC Microprocessor CHRP bindings 1.2b - page 47 */
  /* can't start with IBMA */
  if (memcmp(block, ebdic_ibma, sizeof(ebdic_ibma)) == 0)
    return 0;
  /* must have LE 0xAA55 signature at offset 510 */
  if (block[511] != 0xAA && block[510] != 0x55)
    return 0;
  /* valid 16 bit LE bytes per sector - 256, 512, 1024 */
  if (block[11] != 0
      || (block[12] != 1 && block[12] != 2 && block[12] != 4))
    return 0;
  /* nr fats is 1 or 2 */
  if (block[16] != 1 && block[16] != 2)
    return 0;
  return 1;
}


/* Verify that the device contains an ISO-9660 File system */

static int
is_iso9660(device_instance *raw_disk)
{
  /* ref PowerPC Microprocessor CHRP bindings 1.2b - page 47 */
  unsigned8 block[512];
  if (device_instance_seek(raw_disk, 0, 512 * 64) < 0)
    return 0;
  if (device_instance_read(raw_disk, block, sizeof(block)) != sizeof(block))
    return 0;
  if (block[0] == 0x01
      && block[1] == 'C'
      && block[2] == 'D'
      && block[3] == '0'
      && block[4] == '0'
      && block[5] == '1')
    return 1;
  return 0;
}


/* Verify that the disk block contains a valid DOS partition table.
   While we're at it have a look around for active partitions etc.

   Return 0: invalid
   Return 1..4: valid, value returned is the first active partition
   Return -1: no active partition */

static int
block0_is_fdisk(const unsigned8 block[])
{
  const int partition_type_fields[] = { 0, 0x1c2, 0x1d2, 0x1e2, 0x1f2 };
  const int partition_active_fields[] = { 0, 0x1be, 0x1ce, 0x1de, 0xee };
  int partition;
  int active = -1;
  /* ref PowerPC Microprocessor CHRP bindings 1.2b - page 47 */
  /* must have LE 0xAA55 signature at offset 510 */
  if (block[511/*0x1ff*/] != 0xAA && block[510/*0x1fe*/] != 0x55)
    return 0;
  /* must contain valid partition types */
  for (partition = 1; partition <= 4 && active != 0; partition++) {
    int partition_type = block[partition_type_fields[partition]];
    int is_active = block[partition_active_fields[partition]] == 0x80;
    const char *type;
    switch (partition_type) {
    case 0x00:
      type = "UNUSED";
      break;
    case 0x01:
      type = "FAT 12 File system";
      break;
    case 0x04:
      type = "FAT 16 File system";
      break;
    case 0x05:
    case 0x06:
      type = "rejected - extended/chained partition not supported";
      active = 0;
      break;
    case 0x41:
      type = "Single program image";
      break;
    case 0x82:
      type = "Solaris?";
      break;
    case 0x96:
      type = "ISO 9660 File system";
      break;
    default:
      type = "rejected - unknown type";
      active = 0;
      break;
    }
    PTRACE(disklabel, ("partition %d of type 0x%02x - %s%s\n",
		       partition,
		       partition_type,
		       type,
		       is_active && active != 0 ? " (active)" : ""));
    if (partition_type != 0 && is_active && active < 0)
      active = partition;
  }
  return active;
}


/* Verify that block0 corresponds to a MAC disk */

static int
block0_is_mac_disk(const unsigned8 block[])
{
  /* ref PowerPC Microprocessor CHRP bindings 1.2b - page 47 */
  /* signature - BEx4552 at offset 0 */
  if (block[0] != 0x45 || block[1] != 0x52)
    return 0;
  return 1;
}


/* Open a logical disk/file */

device_instance *
pk_disklabel_create_instance(device_instance *raw_disk,
			     const char *args)
{
  int partition;
  char *filename;

  /* parse the arguments */
  if (args == NULL) {
    partition = 0;
    filename = NULL;
  }
  else {
    partition = strtoul((char*)args, &filename, 0);
    if (filename == args)
      partition = -1; /* not specified */
    if (*filename == ',')
      filename++;
    if (*filename == '\0')
      filename = NULL; /* easier */
  }

  if (partition == 0) {
    /* select the raw disk */
    return raw_disk;
  }
  else {
    unsigned8 boot_block[512];
    /* get the boot block for examination */
    if (device_instance_seek(raw_disk, 0, 0) < 0)
      device_error(device_instance_device(raw_disk),
		   "Problem seeking on raw disk");
    if (device_instance_read(raw_disk, &boot_block, sizeof(boot_block))
	!= sizeof(boot_block))
      device_error(device_instance_device(raw_disk), "Problem reading boot block");

    if (partition < 0) {
      /* select the active partition */
      if (block0_is_bpb(boot_block)) {
	device_error(device_instance_device(raw_disk), "Unimplemented active BPB");
      }
      else if (block0_is_fdisk(boot_block)) {
	int active = block0_is_fdisk(boot_block);
	device_error(device_instance_device(raw_disk), "Unimplemented active FDISK (%d)",
		     active);
      }
      else if (is_iso9660(raw_disk)) {
	device_error(device_instance_device(raw_disk), "Unimplemented active ISO9660");
      }
      else if (block0_is_mac_disk(boot_block)) {
	device_error(device_instance_device(raw_disk), "Unimplemented active MAC DISK");
      }
      else {
	device_error(device_instance_device(raw_disk), "Unreconized bootblock");
      }
    }
    else {
      /* select the specified disk partition */
      if (block0_is_bpb(boot_block)) {
	device_error(device_instance_device(raw_disk), "Unimplemented BPB");
      }
      else if (block0_is_fdisk(boot_block)) {
	/* return an instance */
	ppcboot_partition_t *partition_table = (ppcboot_partition_t*) &boot_block[446];
	ppcboot_partition_t *partition_entry;
	disklabel *label;
	if (partition > 4)
	  device_error(device_instance_device(raw_disk),
		       "Only FDISK partitions 1..4 supported");
	partition_entry = &partition_table[partition - 1];
	label = ZALLOC(disklabel);
	label->raw_disk = raw_disk;
	label->pos = 0;
	label->sector_begin = 512 * sector2uw(partition_entry->sector_begin);
	label->sector_length = 512 * sector2uw(partition_entry->sector_length);
	PTRACE(disklabel, ("partition %ld, sector-begin %ld, length %ld\n",
			   (long)partition,
			   (long)label->sector_begin,
			   (long)label->sector_length));
	if (filename != NULL)
	  device_error(device_instance_device(raw_disk),
		       "FDISK file names not yet supported");
	return device_create_instance_from(NULL, raw_disk,
					   label,
					   NULL, args,
					   &package_disklabel_callbacks);
      }
      else if (block0_is_mac_disk(boot_block)) {
	device_error(device_instance_device(raw_disk), "Unimplemented MAC DISK");
      }
      else {
	device_error(device_instance_device(raw_disk),
		     "Unreconized bootblock");
      }
    }
  }
  
  return NULL;
}
  
  
#endif /* _PK_DISKLABEL_C_ */

