/*-
 * Copyright (c) 2002-2018 The UbixOS Project.
 * All rights reserved.
 *
 * This was developed by Christopher W. Olsen for the UbixOS Project.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 * 1) Redistributions of source code must retain the above copyright notice, this list of
 *    conditions, the following disclaimer and the list of authors.
 * 2) Redistributions in binary form must reproduce the above copyright notice, this list of
 *    conditions, the following disclaimer and the list of authors in the documentation and/or
 *    other materials provided with the distribution.
 * 3) Neither the name of the UbixOS Project nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <pci/hd.h>
#include <sys/io.h>
#include <sys/device.h>
#include <lib/kmalloc.h>
#include <lib/kprintf.h>
#include <devfs/devfs.h>
#include <string.h>
#include <fs/common/gpt.h>

static const uuid_t freebsd_ufs_uuid = GPT_ENT_TYPE_FREEBSD_UFS;

int initHardDisk() {
  int i = 0x0;
  int x = 0x0;
  int minor = 0x0;
  struct device_interface *devInfo = 0x0;
  struct device_interface *devInfo2 = 0x0;
  struct dos_partition *d = 0x0;
  struct driveInfo *hdd = 0x0;
  struct driveInfo *hdd2 = 0x0;
  char *data = 0x0;
  char *data2 = 0x0;
  char name[16];
  struct bsd_disklabel *bsdd = 0x0;

  //GPT
  char *secbuf = 0x0;

  hdd = (struct driveInfo *) kmalloc(sizeof(struct driveInfo));
  hdd->ata_identify = (struct ata_identify_data *) kmalloc(sizeof(struct ata_identify_data));
  hdd->hdPort = 0x1F0;
  hdd->hdDev = 0x40;
  hdd->parOffset = 0x0;
  hdd->part = 0x2;

  /* Alloc memory for device structure and set it up correctly */
  devInfo = (struct device_interface *) kmalloc(sizeof(struct device_interface));
  devInfo->read = (void *) &hdRead;
  devInfo->write = (void *) &hdWrite;
  devInfo->reset = (void *) &hdReset;
  devInfo->init = (void *) &hdInit;
  devInfo->ioctl = (void *) &hdIoctl;
  devInfo->stop = (void *) &hdStop;
  devInfo->start = (void *) &hdStart;
  devInfo->standby = (void *) &hdStandby;
  devInfo->info = hdd;

  devInfo->major = 0x1;

  data = (char *) kmalloc(512);
  d = (struct dos_partition *) (data + 0x1BE);

  data2 = (char *) kmalloc(512);
  bsdd = (struct bsd_disklabel *) data2;

  if (device_add(0, 'c', devInfo) == 0x0) {
    kprintf("ad0 - Start: [0x0], Size: [0x%x/0x%X]\n", hdd->hdSize, hdd->hdSize * 512);
    devfs_makeNode("ad0", 'b', 0x1, 0x0);
    hdRead(devInfo->info, data, 0x0, 0x1);

    if (d[0].dp_type == 0xEE) {
      // MrOlsen (2016-01-14) DEBUG: This was just to help debug
      //kprintf( "%s - Type: [0x%X - %s], Start: [0x%X], Size: [0x%X]\n", name, d[0].dp_type, (d[0].dp_type >= 0 && d[0].dp_type <= 255) ? part_types[d[0].dp_type] : "Unknown", d[0].dp_start, d[0].dp_size );

      secbuf = (char *) kmalloc(65536);

      if (gptread(&freebsd_ufs_uuid, devInfo, secbuf) == -1) {
        kprintf("%s: unable to load GPT\n", "KERNEL");
        //return (-1);
      }
      else {
        devInfo2 = (struct device_interface *) kmalloc(sizeof(struct device_interface));
        hdd2 = (struct driveInfo *) kmalloc(sizeof(struct driveInfo));
        memcpy(devInfo2, devInfo, sizeof(struct device_interface));
        memcpy(hdd2, hdd, sizeof(struct driveInfo));
        //hdd2->parOffset = d[i].dp_start;
        devInfo2->info = hdd2;
        minor++;

        if (gptfind(&freebsd_ufs_uuid, devInfo2, hdd->part) == -1) {
          //MrOlsen (2016-01-11) FIX: I am using "1" as partition 1
          kprintf("%s: no UFS partition was found\n", "KERNEL");
          //return (-1);
        }

//MrOlsen (2016-01-14) DEBUG: This was just debugging code
//kprintf("[%i - %i]\n", hdd->parOffset, hdd2->parOffset);

        if (device_add(minor, 'c', devInfo2) == 0x0) {
          sprintf(name, "ad0p%i", hdd->part);
          kprintf("%s - Type: [0x%X - %s], Start: [%i], Offset: [%i], Size: [%i]\n", name, d[0].dp_type, (d[0].dp_type >= 0 && d[0].dp_type <= 255) ? part_types[d[0].dp_type] : "Unknown", hdd2->lba_start, hdd2->parOffset, hdd2->lba_end - hdd2->lba_start);
          devfs_makeNode(name, 'c', 0x1, minor);
        }
      }

    }
    else {

      for (i = 0x0; i < 0x4; i++) {

        //MrOlsen (2016-01-14) DEBUG: This was just debugging code
        //kprintf( "Type: 0x%X\n", d[i].dp_type );

        if (d[i].dp_type == 0xEE)
          kprintf("Motherfucker Jones! We're GPT.... Lick My Nuts Now Bitch!\n");

        if (d[i].dp_type != 0x0) {
          devInfo2 = (struct device_interface *) kmalloc(sizeof(struct device_interface));
          hdd2 = (struct driveInfo *) kmalloc(sizeof(struct driveInfo));
          memcpy(devInfo2, devInfo, sizeof(struct device_interface));
          memcpy(hdd2, hdd, sizeof(struct driveInfo));
          hdd2->parOffset = d[i].dp_start;
          devInfo2->info = hdd2;
          minor++;

          if (device_add(minor, 'c', devInfo2) == 0x0) {
            sprintf(name, "ad0s%i", i + 1);
            kprintf("%s - Type: [0x%X - %s], Start: [0x%X], Size: [0x%X]\n", name, d[i].dp_type, (d[i].dp_type >= 0 && d[i].dp_type <= 255) ? part_types[d[i].dp_type] : "Unknown", d[i].dp_start, d[i].dp_size);
            devfs_makeNode(name, 'c', 0x1, minor);

            if (d[i].dp_type == 0xA5) {
              //Why do i need to add 1?
              hdRead(devInfo->info, data2, d[i].dp_start + 1, 0x1);

              for (x = 0; x < bsdd->d_npartitions; x++) {
                if (bsdd->d_partitions[x].p_size > 0) {
                  sprintf(name, "ad0s%i%c", i + 1, 'a' + x);
                  //New Nodes
                  devInfo2 = (struct device_interface *) kmalloc(sizeof(struct device_interface));
                  hdd2 = (struct driveInfo *) kmalloc(sizeof(struct driveInfo));
                  memcpy(devInfo2, devInfo, sizeof(struct device_interface));
                  memcpy(hdd2, hdd, sizeof(struct driveInfo));
                  //hdd2->parOffset = d[i].dp_start + bsdd->d_partitions[x].p_offset;
                  hdd2->lba_start = d[i].dp_start;
                  hdd2->parOffset = bsdd->d_partitions[x].p_offset;
                  devInfo2->info = hdd2;
                  minor++;
                  device_add(minor, 'c', devInfo2);
                  devfs_makeNode(name, 'c', 0x1, minor);
                  kprintf("%s - Type: [%s], Start: [0x%X], Size: [0x%X], MM: [%i:%i]\n", name, fstypenames[bsdd->d_partitions[x].p_fstype], bsdd->d_partitions[x].p_offset, bsdd->d_partitions[x].p_size, devInfo->major, minor);
                }

              }

            }

          }

        }

      }
    }
  }
  kfree(data);
  return (0x0);
}

int hdStandby() {
  /* Not Implemented */
  return (0x0);
}

int hdStart() {
  /* Not Implemented */
  return (0x0);
}

int hdStop() {
  /* Not Implemented */
  return (0x0);
}

int hdIoctl() {
  /* Not Implemented */
  return (0x0);
}

int hdReset() {
  /* Not Implemented */
  return (0x0);
}

int hdInit(struct device_node *dev) {
  u_int8_t retVal = 0x0;
  int counter = 0x0;
  u_int16_t *tmp = 0x0;
  struct driveInfo *hdd = dev->devInfo->info;

  for (counter = 1000000; counter >= 0; counter--) {
    retVal = inportByte(hdd->hdPort + ATA_COMMAND) & ATA_S_BUSY;

    if (!retVal)
      goto ready;

  }

  kprintf("Error Initializing Drive\n");
  return (1);

  ready:

  outportByte(hdd->hdPort + ATA_DRIVE, hdd->hdDev);
  outportByte(hdd->hdPort + ATA_COMMAND, ATA_IDENTIFY);

  //for (counter = 1000000; counter >= 0; counter--) {
  for (counter = 10000000; counter >= 0; counter--) {
    retVal = inportByte(hdd->hdPort + ATA_COMMAND);
    if ((retVal & 1) != 0x0) {
      kprintf("Error Drive Not Available\n");
      return (1);
    }
    if ((retVal & 8) != 0x0) {
      goto go;
    }
  }

  kprintf("Time Out Waiting On Drive\n");
  return (1);

  go:

  tmp = (u_int16_t *) hdd->ata_identify;

  for (counter = 0; counter < 256; counter++) {
    tmp[counter] = inportWord(hdd->hdPort + ATA_DATA);
  }

  retVal = tmp[0x2F] & 0xFF;

  switch (retVal) {
    case 0:
      hdd->hdShift = 0;
      hdd->hdMulti = 1;
    break;
    case 2:
      hdd->hdShift = 1;
      hdd->hdMulti = retVal;
    break;
    case 4:
      hdd->hdShift = 2;
      hdd->hdMulti = retVal;
    break;
    case 8:
      hdd->hdShift = 3;
      hdd->hdMulti = retVal;
    break;
    case 16:
      hdd->hdShift = 4;
      hdd->hdMulti = retVal;
    break;
    case 32:
      hdd->hdShift = 5;
      hdd->hdMulti = retVal;
    break;
    case 64:
      hdd->hdShift = 6;
      hdd->hdMulti = retVal;
    break;
    case 128:
      hdd->hdShift = 7;
      hdd->hdMulti = retVal;
    break;
    default:
      kprintf("Error BLOCK Mode Unavailable: [%x]\n", retVal);
      return (1);
  }

  if (hdd->ata_identify->command_set_enabled1 & ATA_IDENTIFY_COMMAND_SET_SUPPORTED1_48BIT_ENABLE) {
    hdd->lba_high = hdd->ata_identify->max_48bit_lba[7] << 24;
    hdd->lba_high |= hdd->ata_identify->max_48bit_lba[6] << 16;
    hdd->lba_high |= hdd->ata_identify->max_48bit_lba[5] << 8;
    hdd->lba_high |= hdd->ata_identify->max_48bit_lba[4];

    hdd->lba_low = hdd->ata_identify->max_48bit_lba[3] << 24;
    hdd->lba_low |= hdd->ata_identify->max_48bit_lba[2] << 16;
    hdd->lba_low |= hdd->ata_identify->max_48bit_lba[1] << 8;
    hdd->lba_low |= hdd->ata_identify->max_48bit_lba[0];
  }
  else {
    hdd->lba_high = 0;
    hdd->lba_low = hdd->ata_identify->total_num_sectors[3] << 24;
    hdd->lba_low |= hdd->ata_identify->total_num_sectors[2] << 16;
    hdd->lba_low |= hdd->ata_identify->total_num_sectors[1] << 8;
    hdd->lba_low |= hdd->ata_identify->total_num_sectors[0];
  }

  // If the ATA device reports its sector size (bit 12 of Word 106), then use that instead.
  if (hdd->ata_identify->physical_logical_sector_info & ATA_IDENTIFY_SECTOR_LARGER_THEN_512_ENABLE) {
    hdd->sector_size = hdd->ata_identify->words_per_logical_sector[3] << 24;
    hdd->sector_size |= hdd->ata_identify->words_per_logical_sector[2] << 16;
    hdd->sector_size |= hdd->ata_identify->words_per_logical_sector[1] << 8;
    hdd->sector_size |= hdd->ata_identify->words_per_logical_sector[0];
  }
  else {
    // Default the sector size to 512 bytes
    hdd->sector_size = 512;
  }

  kprintf("LBA [0x%X - 0x%X], LBA_HIGH: %i, LBA_LOW: %i, SECTOR_SIZE: %i\n", hdd->ata_identify->command_set_enabled1, hdd->ata_identify->command_set_enabled1 & ATA_IDENTIFY_COMMAND_SET_SUPPORTED1_48BIT_ENABLE, hdd->lba_high, hdd->lba_low, hdd->sector_size);

  outportByte(hdd->hdPort + hdSecCount, retVal);
  outportByte(hdd->hdPort + hdHead, hdd->hdDev);
  outportByte(hdd->hdPort + hdCmd, 0xC6);

  hdd->hdMask = retVal;
  //hdd->hdSize = (hdd->hdSector[0x7B] * 256 * 256 * 256) + (hdd->hdSector[0x7A] * 256 * 256) + (hdd->hdSector[0x79] * 256) + hdd->hdSector[0x78];

  // MrOlsen (2016-01-11) NOTE: Must phase out hdSize
  hdd->hdSize = hdd->lba_low + hdd->lba_high;
  hdd->hdEnable = 1;

  kprintf("Drive: [0x%X/0x%X], Size: [%i Sectors/%i MB]\n", hdd->hdPort, hdd->hdDev, hdd->hdSize, hdd->hdSize / 2048);
  dev->devInfo->size = hdd->hdSize * 512;
  dev->devInfo->initialized = 0x1;
  return (0x0);
}

int hdWrite(struct driveInfo *hdd, void *baseAddr, uInt32 startSector, uInt32 sectorCount) {
  long counter = 0x0;
  long retVal = 0x0;
  short transactionCount = 0x0;
  short *tmp = (short *) baseAddr;
  if (hdd->lba_start == 0)
    startSector += hdd->parOffset;
  else
    startSector += hdd->lba_start;

  if (hdd->hdEnable == 0x0) {
    kprintf("Invalid Drive\n");
    return (1);
  }
  if ((sectorCount >> hdd->hdShift) == 0x0) {
    hdd->hdCalc = sectorCount; /* hdd->hdMask; */
    transactionCount = 1;
  }
  else {
    hdd->hdCalc = hdd->hdMulti;
    transactionCount = sectorCount >> hdd->hdShift;
  }
  for (; transactionCount > 0; transactionCount--) {
    //for (counter = 1000000; counter >= 0; counter--) {
    for (counter = 10000000; counter >= 0; counter--) {
      retVal = inportByte(hdd->hdPort + hdStat) & 0x80;
      if (!retVal)
        goto ready;
    }
    kprintf("Time Out Waiting On Drive\n");
    return (1);
    ready: outportByte(hdd->hdPort + hdSecCount, hdd->hdCalc);
    outportByte(hdd->hdPort + hdSecNum, (startSector & 0xFF));
    retVal = startSector >> 8;
    outportByte(hdd->hdPort + hdCylLow, (retVal & 0xFF));
    retVal >>= 8;
    outportByte(hdd->hdPort + hdCylHi, (retVal & 0xFF));
    retVal >>= 8;
    retVal &= 0x0F;
    retVal |= (hdd->hdDev | 0xA0); //Test As Per TJ
    outportByte(hdd->hdPort + hdHead, (retVal & 0xFF));
    if (hdd->hdShift > 0)
      outportByte(hdd->hdPort + hdCmd, 0xC5);
    else
      outportByte(hdd->hdPort + hdCmd, 0x30);
    //for (counter = 1000000; counter >= 0; counter--) {
    for (counter = 10000000; counter >= 0; counter--) {
      retVal = inportByte(hdd->hdPort + hdStat);
      if ((retVal & 1) != 0x0) {
        kprintf("HD Write Error\n");
        return (1);
      }
      if ((retVal & 8) != 0x0) {
        goto go;
      }
    }
    kprintf("Time Out Waiting On Drive\n");
    return (1);
    go: for (counter = 0; counter < (hdd->hdCalc << 8); counter++) {
      outportWord(hdd->hdPort + hdData, (short) tmp[counter]);
    }
    tmp += (counter + 0);
    startSector += hdd->hdCalc;
  }
  return (0);
}

int hdRead(struct driveInfo *hdd, void *baseAddr, uInt32 startSector, uInt32 sectorCount) {
  long counter = 0x0;
  long retVal = 0x0;
  short transactionCount = 0x0;
  short *tmp = (short *) baseAddr;
  startSector += hdd->parOffset;
  startSector += hdd->lba_start;

  if (hdd->hdEnable == 0x0) {
    kprintf("Invalid Drive\n");
    return (1);
  }
  if ((sectorCount >> hdd->hdShift) == 0x0) {
    hdd->hdCalc = sectorCount; /* hdd->hdMask); */
    transactionCount = 1;
  }
  else {
    hdd->hdCalc = hdd->hdMulti;
    transactionCount = sectorCount >> hdd->hdShift;
  }
  for (; transactionCount > 0; transactionCount--) {
    //for (counter = 1000000; counter >= 0; counter--) {
    for (counter = 10000000; counter >= 0; counter--) {
      retVal = inportByte(hdd->hdPort + hdStat) & 0x80;
      if (!retVal)
        goto ready;
    }
    kprintf("Time Out Waiting On Drive\n");
    return (1);
    ready: outportByte(hdd->hdPort + hdSecCount, hdd->hdCalc);
    outportByte(hdd->hdPort + hdSecNum, (startSector & 0xFF));
    retVal = startSector >> 8;
    outportByte(hdd->hdPort + hdCylLow, (retVal & 0xFF));
    retVal >>= 8;
    outportByte(hdd->hdPort + hdCylHi, (retVal & 0xFF));
    retVal >>= 8;
    retVal &= 0x0F;
    retVal |= (hdd->hdDev | 0xA0); //Test as per TJ
    //retVal |= hdd->hdDev; //retVal |= (hdd->hdDev | 0xA0); //Test as per TJ
    outportByte(hdd->hdPort + hdHead, (retVal & 0xFF));
    if (hdd->hdShift > 0)
      outportByte(hdd->hdPort + hdCmd, 0xC4);
    else
      outportByte(hdd->hdPort + hdCmd, 0x20);
    //for (counter = 1000000; counter >= 0; counter--) {
    for (counter = 10000000; counter >= 0; counter--) {
      retVal = inportByte(hdd->hdPort + hdStat);
      if ((retVal & 1) != 0x0) {
        kprintf("HD Read Error: [%i:0x%X:%i]\n", counter, (uInt32) baseAddr, startSector);
        return (1);
      }
      if ((retVal & 8) != 0x0) {
        goto go;
      }
    }
    kprintf("Error: Time Out Waiting On Drive\n");
    return (1);
    go: for (counter = 0; counter < (hdd->hdCalc << 8); counter++) {
      tmp[counter] = inportWord(hdd->hdPort + hdData);
//kprintf("[0x%X]", tmp[counter]);
    }
    tmp += (counter + 0);
    startSector += hdd->hdCalc;
  }
  return (0);
}

/***
 $Log: hd.c,v $
 Revision 1.5  2006/10/12 15:00:26  reddawg
 More changes

 Revision 1.4  2006/10/10 14:14:01  reddawg
 UFS Reading

 Revision 1.3  2006/10/09 02:58:05  reddawg
 Fixing UFS

 Revision 1.2  2006/10/06 15:48:01  reddawg
 Starting to make ubixos work with UFS2

 Revision 1.1.1.1  2006/06/01 12:46:16  reddawg
 ubix2

 Revision 1.2  2005/10/12 00:13:37  reddawg
 Removed

 Revision 1.1.1.1  2005/09/26 17:24:34  reddawg
 no message

 Revision 1.16  2004/08/26 22:51:19  reddawg
 TCA touched me :( i think he likes men....


 sched.h:        kTask_t added parentPid
 endtask.c:     fixed term back to parentPid
 exec.c:          cleaned warnings
 fork.c:            fixed term to childPid
 sched.c:         clean up for dead tasks
 systemtask.c: clean up dead tasks
 kmalloc.c:       cleaned up warnings
 udp.c:            cleaned up warnings
 bot.c:             cleaned up warnings
 shell.c:           cleaned up warnings
 tcpdump.c:     took a dump
 hd.c:             cleaned up warnings
 ubixfs.c:        stopped prning debug info

 Revision 1.15  2004/08/15 00:33:02  reddawg
 Wow the ide driver works again

 Revision 1.14  2004/08/14 21:56:44  reddawg
 Added initialized byte to the device system to make it easy to add child devices which use parent hardware.

 Revision 1.13  2004/08/02 11:43:17  reddawg
 Fixens

 Revision 1.12  2004/07/21 10:02:09  reddawg
 devfs: renamed functions
 device system: renamed functions
 fdc: fixed a few potential bugs and cleaned up some unused variables
 strol: fixed definition
 endtask: made it print out freepage debug info
 kmalloc: fixed a huge memory leak we had some unhandled descriptor insertion so some descriptors were lost
 ld: fixed a pointer conversion
 file: cleaned up a few unused variables
 sched: broke task deletion
 kprintf: fixed ogPrintf definition

 Revision 1.11  2004/05/19 23:36:52  reddawg
 Bug Fixes

 Revision 1.10  2004/05/19 15:20:06  reddawg
 Fixed reference problems due to changes in drive subsystem

 Revision 1.9  2004/05/19 15:07:59  reddawg
 Typo defInfo should of been devInfo

 Revision 1.7  2004/05/19 04:07:43  reddawg
 kmalloc(size,pid) no more it is no kmalloc(size); the way it should of been

 Revision 1.6  2004/04/28 21:10:40  reddawg
 Lots Of changes to make it work with existing os

 Revision 1.5  2004/04/28 02:37:34  reddawg
 More updates for using the new driver subsystem

 Revision 1.4  2004/04/28 02:22:54  reddawg
 This is a fiarly large commit but we are starting to use new driver model
 all around

 Revision 1.3  2004/04/27 21:05:19  reddawg
 Updating drivers to use new model

 Revision 1.2  2004/04/26 22:22:33  reddawg
 DevFS now uses correct size of device

 Revision 1.1.1.1  2004/04/15 12:07:16  reddawg
 UbixOS v1.0

 Revision 1.12  2004/04/13 16:36:33  reddawg
 Changed our copyright, it is all now under a BSD-Style license

 END
 ***/
