/*
 * Copyright (C) 2006-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.
 * --------------------------------------------------------------------
 *
 * This code is based on:
 *
 *  ROM BIOS for use with Bochs/Plex86/QEMU emulation environment
 *
 *  Copyright (C) 2002  MandrakeSoft S.A.
 *
 *    MandrakeSoft S.A.
 *    43, rue d'Aboukir
 *    75002 Paris - France
 *    http://www.linux-mandrake.com/
 *    http://www.mandrakesoft.com/
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 *
 */


#include <stdint.h>

/* Must be defined here (EBDA structures depend on these). */
#define BX_MAX_ATA_INTERFACES   4
#define BX_MAX_ATA_DEVICES      (BX_MAX_ATA_INTERFACES*2)

#define BX_USE_ATADRV           1
#define BX_ELTORITO_BOOT        1

#ifdef VBOX_WITH_SCSI
    /* Enough for now */
    #define BX_MAX_SCSI_DEVICES 4
    /* A SCSI device starts always at BX_MAX_ATA_DEVICES. */
    #define VBOX_IS_SCSI_DEVICE(device_id) (device_id >= BX_MAX_ATA_DEVICES)
    #define VBOX_GET_SCSI_DEVICE(device_id) (device_id - BX_MAX_ATA_DEVICES)
#else
    #define BX_MAX_SCSI_DEVICES 0
#endif

#ifdef VBOX_WITH_AHCI
    /* Four should be enough for now */
    #define BX_MAX_AHCI_DEVICES 4

    /* An AHCI device starts always at BX_MAX_ATA_DEVICES + BX_MAX_SCSI_DEVICES. */
    #define VBOX_IS_AHCI_DEVICE(device_id) (device_id >= (BX_MAX_ATA_DEVICES + BX_MAX_SCSI_DEVICES))
    #define VBOX_GET_AHCI_DEVICE(device_id) (device_id - (BX_MAX_ATA_DEVICES + BX_MAX_SCSI_DEVICES))
#else
    #define BX_MAX_AHCI_DEVICES 0
#endif

#define BX_MAX_STORAGE_DEVICES (BX_MAX_ATA_DEVICES + BX_MAX_SCSI_DEVICES + BX_MAX_AHCI_DEVICES)

/* Generic storage device types. These depend on the controller type and
 * determine which device access routines should be called.
 */
enum dsk_type_enm {
    DSK_TYPE_NONE,      /* Unknown device. */
    DSK_TYPE_UNKNOWN,   /* Unknown ATA device. */
    DSK_TYPE_ATA,       /* ATA disk. */
    DSK_TYPE_ATAPI,     /* ATAPI device. */
    DSK_TYPE_SCSI,      /* SCSI disk. */
    DSK_TYPE_AHCI,      /* SATA disk via AHCI. */
    DSKTYP_CNT          /* Number of disk types. */
};

/* Disk device types. */
//@todo: Do we really need these?
#define DSK_DEVICE_NONE     0x00    /* No device attached. */
#define DSK_DEVICE_HD       0xFF    /* Device is a hard disk. */
#define DSK_DEVICE_CDROM    0x05    /* Device is a CD-ROM. */

/* Geometry translation modes. */
enum geo_xlat_enm {
    GEO_TRANSLATION_NONE,   /* No geometry translation. */
    GEO_TRANSLATION_LBA,    /* LBA translation. */
    GEO_TRANSLATION_LARGE,  /* Large CHS translation. */
    GEO_TRANSLATION_RECHS
};

#if 1 //BX_USE_ATADRV

/* Note: The DPTE and FDPT structures are industry standards and
 * may not be modified. The other disk-related structures are
 * internal to the BIOS.
 */

/* Translated DPT (Device Parameter Table). */
typedef struct {
    uint16_t    iobase1;
    uint16_t    iobase2;
    uint8_t     prefix;
    uint8_t     unused;
    uint8_t     irq;
    uint8_t     blkcount;
    uint8_t     dma;
    uint8_t     pio;
    uint16_t    options;
    uint16_t    reserved;
    uint8_t     revision;
    uint8_t     checksum;
} dpte_t;

ct_assert(sizeof(dpte_t) == 16);    /* Ensure correct size. */

#pragma pack(0)

/* FDPT - Fixed Disk Parameter Table. PC/AT compatible; note
 * that this structure is slightly misaligned.
 */
typedef struct {
    uint16_t    lcyl;
    uint8_t     lhead;
    uint8_t     sig;
    uint8_t     spt;
    uint8_t     resvd1[4];
    uint16_t    cyl;
    uint8_t     head;
    uint8_t     resvd2[2];
    uint8_t     lspt;
    uint8_t     csum;
} fdpt_t;

#pragma pack()

ct_assert(sizeof(fdpt_t) == 16);    /* Ensure correct size. */


/* C/H/S geometry information. */
typedef struct {
    uint16_t    heads;      /* Number of heads. */
    uint16_t    cylinders;  /* Number of cylinders. */
    uint16_t    spt;        /* Number of sectors per track. */
} chs_t;

/* IDE/ATA specific device information. */
typedef struct {
    uint8_t     iface;      /* ISA or PCI. */
    uint8_t     irq;        /* IRQ (on the PIC). */
    uint16_t    iobase1;    /* I/O base 1. */
    uint16_t    iobase2;    /* I/O base 2. */
} ata_chan_t;

#ifdef VBOX_WITH_SCSI

/* SCSI specific device information. */
typedef struct {
    uint16_t    io_base;        /* Port base for HBA communication. */
    uint8_t     target_id;      /* Target ID. */
} scsi_dev_t;

#endif

#ifdef VBOX_WITH_AHCI

/* AHCI specific device information. */
typedef struct {
    uint8_t     port;           /* SATA port. */
} ahci_dev_t;

#endif

/* Generic disk information. */
typedef struct {
    uint8_t     type;         /* Device type (ATA/ATAPI/SCSI/none/unknown). */
    uint8_t     device;       /* Detected type of attached device (HD/CD/none). */
    uint8_t     removable;    /* Removable device flag. */
    uint8_t     lock;         /* Lock count for removable devices. */
    //@todo: ATA specific - move?
    uint8_t     mode;         /* Transfer mode: PIO 16/32 bits - IRQ - ISADMA - PCIDMA. */
    uint8_t     translation;  /* Type of geometry translation. */
    uint16_t    blksize;      /* Disk block size. */
    chs_t       lchs;         /* Logical CHS geometry. */
    chs_t       pchs;         /* Physical CHS geometry. */
    uint32_t    sectors;      /* Total sector count. */
} disk_dev_t;

/* A structure for passing disk request information around. This structure
 * is designed for saving stack space. As BIOS requests cannot be overlapped,
 * one such structure is sufficient.
 */
typedef struct {
    uint32_t    lba;                /* Starting LBA. */
    void __far  *buffer;            /* Read/write data buffer pointer. */
    uint8_t     dev_id;             /* Device ID; index into devices array. */
    uint16_t    nsect;              /* Number of sectors to be transferred. */
    uint16_t    sect_sz;            /* Size of a sector in bytes. */
    uint16_t    cylinder;           /* Starting cylinder (CHS only). */
    uint16_t    head;               /* Starting head (CHS only). */
    uint16_t    sector;             /* Starting sector (CHS only). */
    uint16_t    trsfsectors;        /* Actual sectors transferred. */
    uint32_t    trsfbytes;          /* Actual bytes transferred. */
    uint16_t    skip_b;             /* Bytes to skip before transfer. */
    uint16_t    skip_a;             /* Bytes to skip after transfer. */
} disk_req_t;

extern uint16_t ahci_cmd_packet(uint16_t device_id, uint8_t cmdlen, char __far *cmdbuf,
                                uint16_t header, uint32_t length, uint8_t inout, char __far *buffer);
extern uint16_t scsi_cmd_packet(uint16_t device, uint8_t cmdlen, char __far *cmdbuf,
                                uint16_t header, uint32_t length, uint8_t inout, char __far *buffer);
extern uint16_t ata_cmd_packet(uint16_t device, uint8_t cmdlen, char __far *cmdbuf,
                               uint16_t header, uint32_t length, uint8_t inout, char __far *buffer);

extern uint16_t ata_soft_reset(uint16_t device);

/* All BIOS disk information. Disk-related code in the BIOS should not need
 * anything outside of this structure.
 */
typedef struct {
    disk_req_t  drqp;               /* Disk request packet. */

    /* Bus-independent disk device information. */
    disk_dev_t  devices[BX_MAX_STORAGE_DEVICES];

    uint8_t     hdcount;            /* Total number of BIOS disks. */
    /* Map between (BIOS disk ID - 0x80) and ATA/SCSI/AHCI disks. */
    uint8_t     hdidmap[BX_MAX_STORAGE_DEVICES];

    uint8_t     cdcount;            /* Number of CD-ROMs. */
    /* Map between (BIOS CD-ROM ID - 0xE0) and ATA/SCSI/AHCI devices. */
    uint8_t     cdidmap[BX_MAX_STORAGE_DEVICES];

    /* ATA bus-specific device information. */
    ata_chan_t  channels[BX_MAX_ATA_INTERFACES];

#ifdef VBOX_WITH_SCSI
    /* SCSI bus-specific device information. */
    scsi_dev_t  scsidev[BX_MAX_SCSI_DEVICES];
    uint8_t     scsi_devcount;      /* Number of SCSI devices. */
#endif

#ifdef VBOX_WITH_AHCI
    /* SATA (AHCI) bus-specific device information. */
    ahci_dev_t  ahcidev[BX_MAX_AHCI_DEVICES];
    uint8_t     ahci_devcnt;        /* Number of SATA devices. */
    uint16_t    ahci_seg;           /* Segment of AHCI data block. */
#endif

    dpte_t      dpte;               /* Buffer for building a DPTE. */
} bio_dsk_t;

#if BX_ELTORITO_BOOT
/* El Torito device emulation state. */
typedef struct {
    uint8_t     active;
    uint8_t     media;
    uint8_t     emulated_drive;
    uint8_t     controller_index;
    uint16_t    device_spec;
    uint16_t    buffer_segment;
    uint32_t    ilba;
    uint16_t    load_segment;
    uint16_t    sector_count;
    chs_t       vdevice;        /* Virtual device geometry. */
} cdemu_t;
#endif

// for access to EBDA area
//     The EBDA structure should conform to
//     http://www.frontiernet.net/~fys/rombios.htm document
//     I made the ata and cdemu structs begin at 0x121 in the EBDA seg
/* MS-DOS KEYB.COM may overwrite the word at offset 0x117 in the EBDA
 * which contains the keyboard ID for PS/2 BIOSes.
 */
typedef struct {
    uint8_t     filler1[0x3D];

    fdpt_t      fdpt0;      /* FDPTs for the first two ATA disks. */
    fdpt_t      fdpt1;

    uint8_t     filler2[0xC4];

    bio_dsk_t   bdisk;      /* Disk driver data (ATA/SCSI/AHCI). */

#if BX_ELTORITO_BOOT
    cdemu_t     cdemu;      /* El Torito floppy/HD emulation data. */
#endif

    unsigned char   uForceBootDrive;
    unsigned char   uForceBootDevice;
} ebda_data_t;

ct_assert(sizeof(ebda_data_t) < 0x380);     /* Must be under 1K in size. */

// the last 16 bytes of the EBDA segment are used for the MPS floating
// pointer structure (though only if an I/O APIC is present)

#define EbdaData ((ebda_data_t *) 0)

/* Note: Using fastcall reduces stack usage a little. */
int __fastcall ata_read_sectors(bio_dsk_t __far *bios_dsk);
int __fastcall ata_write_sectors(bio_dsk_t __far *bios_dsk);

int __fastcall scsi_read_sectors(bio_dsk_t __far *bios_dsk);
int __fastcall scsi_write_sectors(bio_dsk_t __far *bios_dsk);

int __fastcall ahci_read_sectors(bio_dsk_t __far *bios_dsk);
int __fastcall ahci_write_sectors(bio_dsk_t __far *bios_dsk);

extern void set_geom_lba(chs_t __far *lgeo, uint32_t nsectors);

// @todo: put this elsewhere (and change/eliminate?)
#define SET_DISK_RET_STATUS(status) write_byte(0x0040, 0x0074, status)

#endif
