/* ----------------------------------------------------------------------- *
 *
 *   Copyright 2009 Erwan Velu - All Rights Reserved
 *
 *   Permission is hereby granted, free of charge, to any person
 *   obtaining a copy of this software and associated documentation
 *   files (the "Software"), to deal in the Software without
 *   restriction, including without limitation the rights to use,
 *   copy, modify, merge, publish, distribute, sublicense, and/or
 *   sell copies of the Software, and to permit persons to whom
 *   the Software is furnished to do so, subject to the following
 *   conditions:
 *
 *   The above copyright notice and this permission notice shall
 *   be included in all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *   OTHER DEALINGS IN THE SOFTWARE.
 *
 * -----------------------------------------------------------------------
 */

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <console.h>

#include "com32io.h"
#include "hdt-ata.h"

#ifdef ATA
/**
 *      ata_id_string - Convert IDENTIFY DEVICE page into string
 *      @id: IDENTIFY DEVICE results we will examine
 *      @s: string into which data is output
 *      @ofs: offset into identify device page
 *      @len: length of string to return. must be an even number.
 *
 *      The strings in the IDENTIFY DEVICE page are broken up into
 *      16-bit chunks.  Run through the string, and output each
 *      8-bit chunk linearly, regardless of platform.
 *
 *      LOCKING:
 *      caller.
 */
void ata_id_string(const uint16_t * id, unsigned char *s,
       unsigned int ofs, unsigned int len)
{
  unsigned int c;

  while (len > 0) {
    c = id[ofs] >> 8;
    *s = c;
    s++;

    c = id[ofs] & 0xff;
    *s = c;
    s++;

    ofs++;
    len -= 2;
  }
}

/**
 *      ata_id_c_string - Convert IDENTIFY DEVICE page into C string
 *      @id: IDENTIFY DEVICE results we will examine
 *      @s: string into which data is output
 *      @ofs: offset into identify device page
 *      @len: length of string to return. must be an odd number.
 *
 *      This function is identical to ata_id_string except that it
 *      trims trailing spaces and terminates the resulting string with
 *      null.  @len must be actual maximum length (even number) + 1.
 *
 *      LOCKING:
 *      caller.
 */
void ata_id_c_string(const uint16_t * id, unsigned char *s,
         unsigned int ofs, unsigned int len)
{
  unsigned char *p;

  //WARN_ON(!(len & 1));

  ata_id_string(id, s, ofs, len - 1);

  p = s + strnlen(s, len - 1);
  while (p > s && p[-1] == ' ')
    p--;
  *p = '\0';
}
#endif

/**
 * Call int 13h, but with retry on failure.  Especially floppies need this.
 */
int int13_retry(const com32sys_t * inreg, com32sys_t * outreg)
{
  int retry = 6;    /* Number of retries */
  com32sys_t tmpregs;

  if (!outreg)
    outreg = &tmpregs;

  while (retry--) {
    __intcall(0x13, inreg, outreg);
    if (!(outreg->eflags.l & EFLAGS_CF))
      return 0; /* CF=0, OK */
  }

  return -1;    /* Error */
}

/* Display CPU registers for debugging purposes */
void printregs(const com32sys_t * r)
{
  printf("eflags = %08x  ds = %04x  es = %04x  fs = %04x  gs = %04x\n"
         "eax = %08x  ebx = %08x  ecx = %08x  edx = %08x\n"
         "ebp = %08x  esi = %08x  edi = %08x  esp = %08x\n",
         r->eflags.l, r->ds, r->es, r->fs, r->gs,
         r->eax.l, r->ebx.l, r->ecx.l, r->edx.l,
         r->ebp.l, r->esi.l, r->edi.l, r->_unused_esp.l);
}

/* Try to get information for a given disk */
int get_disk_params(int disk, struct diskinfo *disk_info)
{
  static com32sys_t getparm, parm, getebios, ebios, inreg, outreg;
  struct device_parameter dp;
#ifdef ATA
  struct ata_identify_device aid;
#endif

  memset(&(disk_info[disk]), 0, sizeof(struct diskinfo));

  disk_info[disk].disk = disk;
  disk_info[disk].ebios = disk_info[disk].cbios = 0;

  /* Sending int 13h func 41h to query EBIOS information */
  memset(&getebios, 0, sizeof(com32sys_t));
  memset(&ebios, 0, sizeof(com32sys_t));

  /* Get EBIOS support */
  getebios.eax.w[0] = 0x4100;
  getebios.ebx.w[0] = 0x55aa;
  getebios.edx.b[0] = disk;
  getebios.eflags.b[0] = 0x3; /* CF set */

  __intcall(0x13, &getebios, &ebios);

  /* Detecting EDD support */
  if (!(ebios.eflags.l & EFLAGS_CF) &&
      ebios.ebx.w[0] == 0xaa55 && (ebios.ecx.b[0] & 1)) {
    disk_info[disk].ebios = 1;
    switch (ebios.eax.b[1]) {
    case 32:
      strlcpy(disk_info[disk].edd_version, "1.0", 3);
      break;
    case 33:
      strlcpy(disk_info[disk].edd_version, "1.1", 3);
      break;
    case 48:
      strlcpy(disk_info[disk].edd_version, "3.0", 3);
      break;
    default:
      strlcpy(disk_info[disk].edd_version, "0", 1);
      break;
    }
  }
  /* Get disk parameters -- really only useful for
   * hard disks, but if we have a partitioned floppy
   * it's actually our best chance...
   */
  memset(&getparm, 0, sizeof(com32sys_t));
  memset(&parm, 0, sizeof(com32sys_t));
  getparm.eax.b[1] = 0x08;
  getparm.edx.b[0] = disk;

  __intcall(0x13, &getparm, &parm);

  if (parm.eflags.l & EFLAGS_CF)
    return disk_info[disk].ebios ? 0 : -1;

  disk_info[disk].heads = parm.edx.b[1] + 1;
  disk_info[disk].sectors_per_track = parm.ecx.b[0] & 0x3f;
  if (disk_info[disk].sectors_per_track == 0) {
    disk_info[disk].sectors_per_track = 1;
  } else {
    disk_info[disk].cbios = 1;  /* Valid geometry */
  }

/* FIXME: memset to 0 make it fails
 * memset(__com32.cs_bounce, 0, sizeof(struct device_pairameter)); */
  memset(&dp, 0, sizeof(struct device_parameter));
  memset(&inreg, 0, sizeof(com32sys_t));

  /* Requesting Extended Read Drive Parameters via int13h func 48h */
  inreg.esi.w[0] = OFFS(__com32.cs_bounce);
  inreg.ds = SEG(__com32.cs_bounce);
  inreg.eax.w[0] = 0x4800;
  inreg.edx.b[0] = disk;

  __intcall(0x13, &inreg, &outreg);

  /* Saving bounce buffer before anything corrupt it */
  memcpy(&dp, __com32.cs_bounce, sizeof(struct device_parameter));

  if (outreg.eflags.l & EFLAGS_CF) {
    printf("Disk 0x%X doesn't supports EDD 3.0\n", disk);
    return -1;
  }

  /* Copying result to the disk_info structure
   * host_bus_type, interface_type, sectors & cylinders */
  snprintf(disk_info[disk].host_bus_type,
     sizeof disk_info[disk].host_bus_type, "%c%c%c%c",
     dp.host_bus_type[0], dp.host_bus_type[1], dp.host_bus_type[2],
     dp.host_bus_type[3]);
  snprintf(disk_info[disk].interface_type,
     sizeof disk_info[disk].interface_type, "%c%c%c%c%c%c%c%c",
     dp.interface_type[0], dp.interface_type[1],
     dp.interface_type[2], dp.interface_type[3],
     dp.interface_type[4], dp.interface_type[5],
     dp.interface_type[6], dp.interface_type[7]);
  disk_info[disk].sectors = dp.sectors;
  disk_info[disk].cylinders = dp.cylinders;

  /*FIXME: we have to find a way to grab the model & fw
   * We do put dummy data until we found a solution */
  snprintf(disk_info[disk].aid.model, sizeof disk_info[disk].aid.model,
     "0x%X", disk);
  snprintf(disk_info[disk].aid.fw_rev, sizeof disk_info[disk].aid.fw_rev,
     "%s", "N/A");
  snprintf(disk_info[disk].aid.serial_no,
     sizeof disk_info[disk].aid.serial_no, "%s", "N/A");

  /* Useless stuff before I figure how to send ata packets */
#ifdef ATA
  memset(__com32.cs_bounce, 0, sizeof(struct device_parameter));
  memset(&aid, 0, sizeof(struct ata_identify_device));
  memset(&inreg, 0, sizeof inreg);
  inreg.ebx.w[0] = OFFS(__com32.cs_bounce + 1024);
  inreg.es = SEG(__com32.cs_bounce + 1024);
  inreg.eax.w[0] = 0x2500;
  inreg.edx.b[0] = disk;

  __intcall(0x13, &inreg, &outreg);

  memcpy(&aid, __com32.cs_bounce, sizeof(struct ata_identify_device));

  if (outreg.eflags.l & EFLAGS_CF) {
    printf("Disk 0x%X: Failed to Identify Device\n", disk);
    //FIXME
    return 0;
  }
//   ata_id_c_string(aid, disk_info[disk].fwrev, ATA_ID_FW_REV, sizeof(disk_info[disk].fwrev));
//   ata_id_c_string(aid, disk_info[disk].model, ATA_ID_PROD,  sizeof(disk_info[disk].model));

  char buff[sizeof(struct ata_identify_device)];
  memcpy(buff, &aid, sizeof(struct ata_identify_device));
  for (int j = 0; j < sizeof(struct ata_identify_device); j++)
    printf("model=|%c|\n", buff[j]);
  printf("Disk 0x%X : %s %s %s\n", disk, aid.model, aid.fw_rev,
         aid.serial_no);
#endif

  return 0;
}
