/* for Linux DOS emulator
 *   Robert Sanders, gt8134b@prism.gatech.edu
 *
 */
#ifndef DISKS_H
#define DISKS_H

#include "fatfs.h"
#define PART_INFO_START		0x1be	/* offset in MBR for partition table */
#define PART_INFO_LEN		0x10	/* size of each partition record */
#define MBR_SIG			0xaa55	/* magic signature */
#define VBR_SIG			0xaa55	/* magic signature */

#define PART_NOBOOT	0
#define PART_BOOT	0x80

#include <assert.h>
#include <stdint.h>
#include <sys/types.h>
#include <fcntl.h>

/* disk file types */
typedef enum {
  NODISK = -1,
  IMAGE = 0, HDISK, FLOPPY, PARTITION, DIR_TYPE, MAXIDX_DTYPES,
  NUM_DTYPES
} disk_t;

const char *disk_t_str(disk_t t);


#define DISK_RDWR	0
#define DISK_RDONLY	1

struct on_disk_bpb {
  uint16_t bytes_per_sector;
  uint8_t sectors_per_cluster;
  uint16_t reserved_sectors;
  uint8_t num_fats;
  uint16_t num_root_entries;
  uint16_t num_sectors_small;
  uint8_t media_type;
  uint16_t sectors_per_fat;
  uint16_t sectors_per_track;
  uint16_t num_heads;
  union {
    struct {
      uint16_t v300_320_hidden_sectors;
      uint16_t v320_num_sectors_large;
    } __attribute__((packed));
    struct {
      uint32_t v331_400_hidden_sectors;
      uint32_t v331_400_num_sectors_large;
    } __attribute__((packed));
  };
  uint8_t v340_400_drive_number;
  uint8_t v340_400_flags;
#define BPB_SIG_V340 0x28
#define BPB_SIG_V400 0x29
  uint8_t v340_400_signature;
  uint32_t v340_400_serial_number;
  char v400_vol_label[11];
  char v400_fat_type[8];
} __attribute__((packed));
static_assert(sizeof(struct on_disk_bpb) == 51,
		"on_disk_bpb size is incorrect");

struct on_disk_bpb7 {
  uint16_t bytes_per_sector;
  uint8_t sectors_per_cluster;
  uint16_t reserved_sectors;
  uint8_t num_fats;
  uint16_t num_root_entries;
  uint16_t num_sectors_small;
  uint8_t media_type;
  uint16_t sectors_per_fat;
  uint16_t sectors_per_track;
  uint16_t num_heads;
  uint32_t hidden_sectors;
  uint32_t num_sectors_large; // this and above are v3.31 equivalent

  uint32_t logical_sectors_per_fat;
  uint16_t mirroring_flags;
  uint16_t version;
  uint32_t root_directory_cluster;
  uint16_t information_sector;
  uint16_t backup_sector;
  char boot_file_name[12];
  uint8_t drive_number;
  uint8_t flags;
#define BPB_SIG_V7_SHORT 0x28
#define BPB_SIG_V7_LONG 0x29
  uint8_t signature;
  uint32_t serial_number;
  char long_vol_label[11];
  char long_fat_type[8];
} __attribute__((packed));
static_assert(sizeof(struct on_disk_bpb7) == 79,
		"on_disk_bpb7 size is incorrect");

#define PTBL_HL_SET(p, f, v) do { \
  (p)->f##_hi = (v) >> 8; \
  (p)->f##_lo = (v) & 0xff; \
} while (0)
#define PTBL_HL_GET(p, f) (((p)->f##_hi << 8) | (p)->f##_lo)
struct on_disk_partition {
  unsigned char bootflag;		/* 0x80 - active */
  unsigned char start_head;
  unsigned char start_sector:6;
  unsigned char start_track_hi:2;
  unsigned char start_track_lo;
  unsigned char OS_type;		/* What partition type */
  unsigned char end_head;
  unsigned char end_sector:6;
  unsigned char end_track_hi:2;
  unsigned char end_track_lo;
  unsigned int num_sect_preceding;	/* starting sector counting from 0 */
  unsigned int num_sectors;		/* nr of sectors in partition */
} __attribute__((packed));
static_assert(sizeof(struct on_disk_partition) == 16,
		"on_disk_partition size is incorrect");

struct on_disk_mbr {
  uint8_t code[PART_INFO_START];
  struct on_disk_partition partition[4];
  uint16_t signature;           /* MBR_SIG 0xaa55 */
} __attribute__((packed));
static_assert(sizeof(struct on_disk_mbr) == 512,
		"on_disk_mbr size is incorrect");

union ubpb {
  struct on_disk_bpb bpb;
  struct on_disk_bpb7 bpb7;
};

struct on_disk_vbr {
  uint8_t code[0x0b];
  union ubpb u;
  uint8_t pad[0x200 - 0x0b - sizeof(union ubpb) - 0x02];
  uint16_t signature;           /* VBR_SIG 0xaa55 */
} __attribute__((packed));
static_assert(sizeof(struct on_disk_vbr) == 512,
		"on_disk_vbr size is incorrect");

struct partition {
  int number;
  struct on_disk_mbr mbr;	/* fake Master Boot Record */
};

/* CMOS types for the floppies */
typedef enum {
  FIVE_INCH_360KFLOP = 1,	/* 5.25 in,  360 K floppy */
  FIVE_INCH_FLOPPY,		/* 5.25 in, 1.2 MB floppy */
  THREE_INCH_720KFLOP,		/* 3.5 in,  720 kB floppy */
  THREE_INCH_FLOPPY,		/* 3.5 in, 1.44 MB floppy */
  THREE_INCH_2880KFLOP,		/* 3.5 in, 2.88 MB floppy */
} floppy_t;

const char *floppy_t_str(floppy_t t);

struct disk {
  char *dev_name;		/* disk file */
  int diskcyl4096;		/* INT13 support for 4096 cylinders */
  int rdonly;			/* The way we opened the disk (only filled in if the disk is open) */
  int boot;			/* This is a boot disk */
  int sectors, heads, tracks;	/* geometry */
  unsigned long start;		/* geometry */
  uint64_t num_secs;		/* total sectors on disk */
  int hdtype;			/* 0 none, IBM Types 1, 2 and 9 */
  floppy_t default_cmos;	/* default CMOS floppy type */
  int drive_num;
  int log_offs;
  unsigned long serial;		/* serial number */
  disk_t type;			/* type of file: image, partition, disk */
  off_t header;			/* compensation for opt. pre-disk header */
  int fdesc;			/* file descriptor */
  int removable;		/* real removable drive, can disappear */
  int floppy;			/* emulating floppy */
  int timeout;			/* seconds between floppy timeouts */
  struct partition part_info;	/* neato partition info */
  fatfs_t *fatfs;		/* for FAT file system emulation */
  int mfs_idx;
  int part_image;               /* partition image */
};

/* NOTE: the "header" element in the structure above can (and will) be
 * negative. This facilitates treating partitions as disks (i.e. using
 * /dev/hda1 with a simulated partition table) by adjusting out the
 * simulated partition table offset...
 */

/*
 * this header appears only in hdimage files
 */
struct image_header {
  char sig[7];			/* always set to "DOSEMU", null-terminated
				   or to "\x0eDEXE" */
  uint32_t heads;
  uint32_t sectors;
  uint32_t cylinders;
  uint32_t header_end;	/* distance from beginning of disk to end of header
			 * i.e. this is the starting byte of the real disk
			 */
  char dummy[1];	/* someone did define the header unaligned,
			 * we correct that at least for the future
			 */
  uint32_t dexeflags;
  char padding[100];
} __attribute__((packed));
static_assert(sizeof(struct image_header) == 128,
		"image_header size is incorrect");

#define IMAGE_MAGIC		"DOSEMU"
#define IMAGE_MAGIC_SIZE	strlen(IMAGE_MAGIC)
#define DEXE_MAGIC		0x5845440e /* 0x0e,'D','E','X' */

#define MAX_FDISKS 4
#define MAX_HDISKS 16
#define SECTOR_SIZE		512

/*
 * Array of disk structures for floppies...
 */
extern struct disk disktab[MAX_FDISKS];

/*
 * Array of disk structures for hard disks...
 *
 * Can be whole hard disks, dos extended partitions (containing one or
 * more partitions) or their images (files)
 */
extern struct disk hdisktab[MAX_HDISKS];

extern struct disk *hdisk_find(uint8_t num);
extern struct disk *hdisk_find_by_path(const char *path);

#define HDISK_NUM(i) ({ assert(hdisktab[i].drive_num & 0x80); \
    (hdisktab[i].drive_num & 0x7f) + 2; })

#define FOR_EACH_HDISK(i, c) do { \
    for (i = 0; i < MAX_HDISKS; i++) { \
        if (!hdisktab[i].drive_num) \
            continue; \
        c \
    } \
} while (0)

#if 1
#ifdef __linux__
#define DISK_OFFSET(dp,h,s,t) \
  (((long long)(t * dp->heads + h) * dp->sectors + s) * SECTOR_SIZE)
#else
#define DISK_OFFSET(dp,h,s,t) \
  (((t * dp->heads + h) * dp->sectors + s) * SECTOR_SIZE)
#endif
#else
#define DISK_OFFSET(dp,h,s,t) \
  (((h * dp->tracks + t) * dp->sectors + s) * SECTOR_SIZE)
#endif

int read_mbr(const struct disk *dp, unsigned buffer);
int read_sectors(const struct disk *, unsigned, uint64_t, long);
int write_sectors(struct disk *, unsigned, uint64_t, long);

void disk_open(struct disk *dp);
int disk_is_bootable(const struct disk *dp);
int disk_root_contains(const struct disk *dp, int file_idx);
int disk_validate_boot_part(struct disk *dp);

void mimic_boot_blk(void);

void fatfs_init(struct disk *);
void fatfs_done(struct disk *);

fatfs_t *get_fat_fs_by_serial(unsigned long serial, int *r_idx, int *r_ro);
fatfs_t *get_fat_fs_by_drive(unsigned char drv_num);

#define floppy_setup	d_nullf

/* int13 error returns */
#define DERR_NOERR	0
#define DERR_BADCMD 	1
#define DERR_BADSEC 	2
#define DERR_WP 	3
#define DERR_NOTFOUND 	4
#define DERR_CHANGE 	6
#define DERR_BOUNDARY	9
#define DERR_ECCERR 	0x10
#define DERR_CONTROLLER	0x20
#define DERR_SEEK	0x40
#define DERR_NOTREADY	0x80
#define DERR_WRITEFLT	0xcc

/* Int13 maximum amount of sectors to access.
 *
 * Mentioned in RBIL 61 Int 1301 Table 00234,
 *  which lists error code 09 as follows:
 *
 * 09h	data boundary error (attempted DMA across 64K boundary or >80h sectors)
 *
 * Note that > 80h sectors with 512 bytes per sector always crosses a 64 KiB
 *  boundary, so it may be the error code is only meant for that crossing.
 *  But in Int 1342 Table 00272, it is also mentioned that the maximum amount
 *  of blocks may be 7Fh (for LBA), and we use the same error code then.
 */
#define I13_MAX_ACCESS	0x80

/* IBM/MS Extensions */
#define IMEXT_MAGIC                 0xaa55
#define IMEXT_VER_MAJOR             0x21
#define IMEXT_API_SUPPORT_BITS      0x01

struct ibm_ms_diskaddr_pkt {
  uint8_t len;                  /* size of packet, 0x10 */
  uint8_t reserved;
  uint16_t blocks;     /* number of blocks to transfer */
  uint16_t buf_ofs;    /* offset of transfer buffer */
  uint16_t buf_seg;    /* segment of transfer buffer */
  uint64_t block;	/* starting block number */
} __attribute__((packed));

struct ibm_ms_drive_params {
  uint16_t len;        /* size of buffer, 0x1a or 0x1e */
  uint16_t flags;      /* information flags */
  uint32_t tracks;           /* physical cylinders */
  uint32_t heads;            /* physical heads */
  uint32_t sectors;          /* physical sectors per track */
  uint32_t total_sectors_lo;
  uint32_t total_sectors_hi;
  uint16_t bytes_per_sector;
  uint16_t edd_cfg_ofs;  /* pointer to EDD configuration parameters */
  uint16_t edd_cfg_seg;
} __attribute__((packed));

/* Values for information flags */
#define IMEXT_INFOFLAG_NODMAERR   0x01
#define IMEXT_INFOFLAG_CHSVALID   0x02
#define IMEXT_INFOFLAG_REMOVABLE  0x04
#define IMEXT_INFOFLAG_WVERIFY    0x08
#define IMEXT_INFOFLAG_CHGLINE    0x10
#define IMEXT_INFOFLAG_LOCK       0x20
#define IMEXT_INFOFLAG_CHSMAX     0x40

#endif /* DISKS_H */
