/////////////////////////////////////////////////////////////////////////
// $Id: rombios.c,v 1.138 2005/05/07 15:55:26 vruppert Exp $
/////////////////////////////////////////////////////////////////////////
//
//  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

// ROM BIOS for use with Bochs/Plex x86 emulation environment

#include "../hvmloader/config.h"

#define HVMASSIST
#undef HVMTEST

// Xen full virtualization does not handle unaligned IO with page crossing.
// Disable 32-bit PIO as a workaround.
#undef NO_PIO32


// ROM BIOS compatability entry points:
// ===================================
// $e05b ; POST Entry Point
// $e2c3 ; NMI Handler Entry Point
// $e3fe ; INT 13h Fixed Disk Services Entry Point
// $e401 ; Fixed Disk Parameter Table
// $e6f2 ; INT 19h Boot Load Service Entry Point
// $e6f5 ; Configuration Data Table
// $e729 ; Baud Rate Generator Table
// $e739 ; INT 14h Serial Communications Service Entry Point
// $e82e ; INT 16h Keyboard Service Entry Point
// $e987 ; INT 09h Keyboard Service Entry Point
// $ec59 ; INT 13h Diskette Service Entry Point
// $ef57 ; INT 0Eh Diskette Hardware ISR Entry Point
// $efc7 ; Diskette Controller Parameter Table
// $efd2 ; INT 17h Printer Service Entry Point
// $f045 ; INT 10 Functions 0-Fh Entry Point
// $f065 ; INT 10h Video Support Service Entry Point
// $f0a4 ; MDA/CGA Video Parameter Table (INT 1Dh)
// $f841 ; INT 12h Memory Size Service Entry Point
// $f84d ; INT 11h Equipment List Service Entry Point
// $f859 ; INT 15h System Services Entry Point
// $fa6e ; Character Font for 320x200 & 640x200 Graphics (lower 128 characters)
// $fe6e ; INT 1Ah Time-of-day Service Entry Point
// $fea5 ; INT 08h System Timer ISR Entry Point
// $fef3 ; Initial Interrupt Vector Offsets Loaded by POST
// $ff53 ; IRET Instruction for Dummy Interrupt Handler
// $ff54 ; INT 05h Print Screen Service Entry Point
// $fff0 ; Power-up Entry Point
// $fff5 ; ASCII Date ROM was built - 8 characters in MM/DD/YY
// $fffe ; System Model ID

// NOTES for ATA/ATAPI driver (cbbochs@free.fr)
//   Features
//     - supports up to 4 ATA interfaces
//     - device/geometry detection
//     - 16bits/32bits device access
//     - pchs/lba access
//     - datain/dataout/packet command support
//
// NOTES for El-Torito Boot (cbbochs@free.fr)
//   - CD-ROM booting is only available if ATA/ATAPI Driver is available
//   - Current code is only able to boot mono-session cds 
//   - Current code can not boot and emulate a hard-disk
//     the bios will panic otherwise
//   - Current code also use memory in EBDA segement. 
//   - I used cmos byte 0x3D to store extended information on boot-device
//   - Code has to be modified modified to handle multiple cdrom drives
//   - Here are the cdrom boot failure codes:
//       1 : no atapi device found
//       2 : no atapi cdrom found
//       3 : can not read cd - BRVD
//       4 : cd is not eltorito (BRVD)
//       5 : cd is not eltorito (ISO TAG)
//       6 : cd is not eltorito (ELTORITO TAG)
//       7 : can not read cd - boot catalog
//       8 : boot catalog : bad header
//       9 : boot catalog : bad platform
//      10 : boot catalog : bad signature
//      11 : boot catalog : bootable flag not set
//      12 : can not read cd - boot image
//
//   ATA driver
//   - EBDA segment. 
//     I used memory starting at 0x121 in the segment
//   - the translation policy is defined in cmos regs 0x39 & 0x3a
//
// TODO :
//
//   int74 
//     - needs to be reworked.  Uses direct [bp] offsets. (?)
//
//   int13:
//     - f04 (verify sectors) isn't complete  (?)
//     - f02/03/04 should set current cyl,etc in BDA  (?)
//     - rewrite int13_relocated & clean up int13 entry code
//
//   NOTES:
//   - NMI access (bit7 of addr written to 70h)
//
//   ATA driver
//   - should handle the "don't detect" bit (cmos regs 0x3b & 0x3c)
//   - could send the multiple-sector read/write commands
//
//   El-Torito
//   - Emulate a Hard-disk (currently only diskette can be emulated) see "FIXME ElTorito Harddisk"
//   - Implement remaining int13_cdemu functions (as defined by El-Torito specs)
//   - cdrom drive is hardcoded to ide 0 device 1 in several places. see "FIXME ElTorito Hardcoded"
//   - int13 Fix DL when emulating a cd. In that case DL is decremented before calling real int13.
//     This is ok. But DL should be reincremented afterwards. 
//   - Fix all "FIXME ElTorito Various"
//   - should be able to boot any cdrom instead of the first one
//
//   BCC Bug: find a generic way to handle the bug of #asm after an "if"  (fixed in 0.16.7)

#define DEBUG_ROMBIOS      0

#define DEBUG_ATA          0
#define DEBUG_INT13_HD     0
#define DEBUG_INT13_CD     0
#define DEBUG_INT13_ET     0
#define DEBUG_INT13_FL     0
#define DEBUG_INT15        0
#define DEBUG_INT16        0
#define DEBUG_INT1A        0
#define DEBUG_INT74        0
#define DEBUG_APM          0

#define BX_CPU           3
#define BX_USE_PS2_MOUSE 1
#define BX_CALL_INT15_4F 1
#define BX_USE_EBDA      1
#define BX_SUPPORT_FLOPPY 1
#define BX_FLOPPY_ON_CNT 37   /* 2 seconds */
#define BX_PCIBIOS       1
#define BX_APM           1

#define BX_USE_ATADRV    1
#define BX_ELTORITO_BOOT 1

#define BX_TCGBIOS       0              /* main switch for TCG BIOS ext. */

#define BX_MAX_ATA_INTERFACES   4
#define BX_MAX_ATA_DEVICES      (BX_MAX_ATA_INTERFACES*2)

#define BX_VIRTUAL_PORTS 1 /* normal output to Bochs ports */
#define BX_DEBUG_SERIAL  0 /* output to COM1 */

   /* model byte 0xFC = AT */
#define SYS_MODEL_ID     0xFC
#define SYS_SUBMODEL_ID  0x00
#define BIOS_REVISION    1
#define BIOS_CONFIG_TABLE 0xe6f5

#ifndef BIOS_BUILD_DATE
#  define BIOS_BUILD_DATE "06/23/99"
#endif

  // 1K of base memory used for Extended Bios Data Area (EBDA)
  // EBDA is used for PS/2 mouse support, and IDE BIOS, etc.
#define EBDA_SEG           0x9FC0
#define EBDA_SIZE          1              // In KiB
#define BASE_MEM_IN_K   (640 - EBDA_SIZE)

  // Define the application NAME
#ifdef HVMASSIST
#  define BX_APPNAME "HVMAssist"
#elif PLEX86
#  define BX_APPNAME "Plex86"
#else
#  define BX_APPNAME "Bochs"
#endif

  // Sanity Checks
#if BX_USE_ATADRV && BX_CPU<3
#    error The ATA/ATAPI Driver can only to be used with a 386+ cpu
#endif
#if BX_USE_ATADRV && !BX_USE_EBDA
#    error ATA/ATAPI Driver can only be used if EBDA is available
#endif
#if BX_ELTORITO_BOOT && !BX_USE_ATADRV
#    error El-Torito Boot can only be use if ATA/ATAPI Driver is available
#endif
#if BX_PCIBIOS && BX_CPU<3
#    error PCI BIOS can only be used with 386+ cpu
#endif
#if BX_APM && BX_CPU<3
#    error APM BIOS can only be used with 386+ cpu
#endif

#ifndef BX_SMP_PROCESSORS
#define BX_SMP_PROCESSORS 1
#    warning BX_SMP_PROCESSORS not defined, defaulting to 1
#endif
  
#define PANIC_PORT  0x400
#define PANIC_PORT2 0x401
#define INFO_PORT   0x402
#define DEBUG_PORT  0x403

// #20  is dec 20
// #$20 is hex 20 = 32
// #0x20 is hex 20 = 32
// LDA  #$20
// JSR  $E820
// LDD  .i,S
// JSR  $C682
// mov al, #$20

// all hex literals should be prefixed with '0x'
//   grep "#[0-9a-fA-F][0-9a-fA-F]" rombios.c
// no mov SEG-REG, #value, must mov register into seg-reg
//   grep -i "mov[ ]*.s" rombios.c

// This is for compiling with gcc2 and gcc3
#define ASM_START #asm
#define ASM_END #endasm

ASM_START
.rom

.org 0x0000

#if BX_CPU >= 3
use16 386
#else
use16 286
#endif

MACRO HALT
  ;; the HALT macro is called with the line number of the HALT call.
  ;; The line number is then sent to the PANIC_PORT, causing Bochs/Plex 
  ;; to print a BX_PANIC message.  This will normally halt the simulation
  ;; with a message such as "BIOS panic at rombios.c, line 4091".
  ;; However, users can choose to make panics non-fatal and continue.
#if BX_VIRTUAL_PORTS
  mov dx,#PANIC_PORT
  mov ax,#?1
  out dx,ax
#else
  mov dx,#0x80
  mov ax,#?1
  out dx,al
#endif
MEND

MACRO JMP_AP
  db 0xea
  dw ?2
  dw ?1
MEND

MACRO SET_INT_VECTOR
  mov ax, ?3
  mov ?1*4, ax
  mov ax, ?2
  mov ?1*4+2, ax
MEND

ASM_END

typedef unsigned char  Bit8u;
typedef unsigned short Bit16u;
typedef unsigned short bx_bool;
typedef unsigned long  Bit32u;


  void memsetb(seg,offset,value,count);
  void memcpyb(dseg,doffset,sseg,soffset,count);
  void memcpyd(dseg,doffset,sseg,soffset,count);
  
  // memset of count bytes
    void 
  memsetb(seg,offset,value,count)
    Bit16u seg;
    Bit16u offset;
    Bit16u value;
    Bit16u count;
  {
  ASM_START
    push bp
    mov  bp, sp
  
      push ax
      push cx
      push es
      push di
  
      mov  cx, 10[bp] ; count
      cmp  cx, #0x00
      je   memsetb_end
      mov  ax, 4[bp] ; segment
      mov  es, ax
      mov  ax, 6[bp] ; offset
      mov  di, ax
      mov  al, 8[bp] ; value
      cld
      rep
       stosb
  
  memsetb_end:
      pop di
      pop es
      pop cx
      pop ax
  
    pop bp
  ASM_END
  }
  
  // memcpy of count bytes
    void 
  memcpyb(dseg,doffset,sseg,soffset,count)
    Bit16u dseg;
    Bit16u doffset;
    Bit16u sseg;
    Bit16u soffset;
    Bit16u count;
  {
  ASM_START
    push bp
    mov  bp, sp
  
      push ax
      push cx
      push es
      push di
      push ds
      push si
  
      mov  cx, 12[bp] ; count
      cmp  cx, #0x0000
      je   memcpyb_end
      mov  ax, 4[bp] ; dsegment
      mov  es, ax
      mov  ax, 6[bp] ; doffset
      mov  di, ax
      mov  ax, 8[bp] ; ssegment
      mov  ds, ax
      mov  ax, 10[bp] ; soffset
      mov  si, ax
      cld
      rep
       movsb
  
  memcpyb_end:
      pop si
      pop ds
      pop di
      pop es
      pop cx
      pop ax
  
    pop bp
  ASM_END
  }

#if 0 
  // memcpy of count dword
    void 
  memcpyd(dseg,doffset,sseg,soffset,count)
    Bit16u dseg;
    Bit16u doffset;
    Bit16u sseg;
    Bit16u soffset;
    Bit16u count;
  {
  ASM_START
    push bp
    mov  bp, sp
  
      push ax
      push cx
      push es
      push di
      push ds
      push si
  
      mov  cx, 12[bp] ; count
      cmp  cx, #0x0000
      je   memcpyd_end
      mov  ax, 4[bp] ; dsegment
      mov  es, ax
      mov  ax, 6[bp] ; doffset
      mov  di, ax
      mov  ax, 8[bp] ; ssegment
      mov  ds, ax
      mov  ax, 10[bp] ; soffset
      mov  si, ax
      cld
      rep
       movsd
  
  memcpyd_end:
      pop si
      pop ds
      pop di
      pop es
      pop cx
      pop ax
  
    pop bp
  ASM_END
  }
#endif

  // read_dword and write_dword functions
  static Bit32u         read_dword();
  static void           write_dword();
  
    Bit32u
  read_dword(seg, offset)
    Bit16u seg;
    Bit16u offset;
  {
  ASM_START
    push bp
    mov  bp, sp
  
      push bx
      push ds
      mov  ax, 4[bp] ; segment
      mov  ds, ax
      mov  bx, 6[bp] ; offset
      mov  ax, [bx]
      inc  bx
      inc  bx
      mov  dx, [bx]
      ;; ax = return value (word)
      ;; dx = return value (word)
      pop  ds
      pop  bx
  
    pop  bp
  ASM_END
  }
  
    void
  write_dword(seg, offset, data)
    Bit16u seg;
    Bit16u offset;
    Bit32u data;
  {
  ASM_START
    push bp
    mov  bp, sp
  
      push ax
      push bx
      push ds
      mov  ax, 4[bp] ; segment
      mov  ds, ax
      mov  bx, 6[bp] ; offset
      mov  ax, 8[bp] ; data word
      mov  [bx], ax  ; write data word
      inc  bx
      inc  bx
      mov  ax, 10[bp] ; data word
      mov  [bx], ax  ; write data word
      pop  ds
      pop  bx
      pop  ax
  
    pop  bp
  ASM_END
  }
  
  // Bit32u (unsigned long) and long helper functions
  ASM_START
  
  ;; and function
  landl:
  landul:
    SEG SS 
      and ax,[di]
    SEG SS 
      and bx,2[di]
    ret
  
  ;; add function
  laddl:
  laddul:
    SEG SS 
      add ax,[di]
    SEG SS 
      adc bx,2[di]
    ret
  
  ;; cmp function
  lcmpl:
  lcmpul:
    and eax, #0x0000FFFF
    shl ebx, #16
    add eax, ebx
    shr ebx, #16
    SEG SS
      cmp eax, dword ptr [di]
    ret
  
  ;; sub function
  lsubl:
  lsubul:
    SEG SS
    sub ax,[di]
    SEG SS
    sbb bx,2[di]
    ret
  
  ;; mul function
  lmull:
  lmulul:
    and eax, #0x0000FFFF
    shl ebx, #16
    add eax, ebx
    SEG SS
    mul eax, dword ptr [di]
    mov ebx, eax
    shr ebx, #16
    ret
  
  ;; dec function
  ldecl:
  ldecul:
    SEG SS
    dec dword ptr [bx]
    ret
  
  ;; or function
  lorl:
  lorul:
    SEG SS
    or  ax,[di]
    SEG SS
    or  bx,2[di]
    ret
  
  ;; inc function
  lincl:
  lincul:
    SEG SS
    inc dword ptr [bx]
    ret
  
  ;; tst function
  ltstl:
  ltstul:
    and eax, #0x0000FFFF
    shl ebx, #16
    add eax, ebx
    shr ebx, #16
    test eax, eax
    ret
  
  ;; sr function
  lsrul:
    mov  cx,di
    jcxz lsr_exit
    and  eax, #0x0000FFFF
    shl  ebx, #16
    add  eax, ebx
  lsr_loop:
    shr  eax, #1
    loop lsr_loop
    mov  ebx, eax
    shr  ebx, #16
  lsr_exit:
    ret
  
  ;; sl function
  lsll:
  lslul:
    mov  cx,di
    jcxz lsl_exit
    and  eax, #0x0000FFFF
    shl  ebx, #16
    add  eax, ebx
  lsl_loop: 
    shl  eax, #1
    loop lsl_loop
    mov  ebx, eax
    shr  ebx, #16
  lsl_exit:
    ret
  
  idiv_:
    cwd
    idiv bx
    ret

  idiv_u:
    xor dx,dx
    div bx
    ret

  ldivul:
    and  eax, #0x0000FFFF
    shl  ebx, #16
    add  eax, ebx
    xor  edx, edx
    SEG SS
    mov  bx,  2[di]
    shl  ebx, #16
    SEG SS
    mov  bx,  [di]
    div  ebx
    mov  ebx, eax
    shr  ebx, #16
    ret

  ASM_END

// for access to RAM area which is used by interrupt vectors
// and BIOS Data Area

typedef struct {
  unsigned char filler1[0x400];
  unsigned char filler2[0x6c];
  Bit16u ticks_low;
  Bit16u ticks_high;
  Bit8u  midnight_flag;
  } bios_data_t;

#define BiosData ((bios_data_t  *) 0)

#if BX_USE_ATADRV
  typedef struct {
    Bit16u heads;      // # heads
    Bit16u cylinders;  // # cylinders
    Bit16u spt;        // # sectors / track
    } chs_t;

  // DPTE definition
  typedef struct {
    Bit16u iobase1;
    Bit16u iobase2;
    Bit8u  prefix;
    Bit8u  unused;
    Bit8u  irq;
    Bit8u  blkcount;
    Bit8u  dma;
    Bit8u  pio;
    Bit16u options;
    Bit16u reserved;
    Bit8u  revision;
    Bit8u  checksum;
    } dpte_t;
 
  typedef struct {
    Bit8u  iface;        // ISA or PCI
    Bit16u iobase1;      // IO Base 1
    Bit16u iobase2;      // IO Base 2
    Bit8u  irq;          // IRQ
    } ata_channel_t;

  typedef struct {
    Bit8u  type;         // Detected type of ata (ata/atapi/none/unknown)
    Bit8u  device;       // Detected type of attached devices (hd/cd/none)
    Bit8u  removable;    // Removable device flag
    Bit8u  lock;         // Locks for removable devices
    // Bit8u  lba_capable;  // LBA capable flag - always yes for bochs devices
    Bit8u  mode;         // transfert mode : PIO 16/32 bits - IRQ - ISADMA - PCIDMA
    Bit16u blksize;      // block size

    Bit8u  translation;  // type of translation
    chs_t  lchs;         // Logical CHS
    chs_t  pchs;         // Physical CHS

    Bit32u sectors;      // Total sectors count
    } ata_device_t;

  typedef struct {
    // ATA channels info
    ata_channel_t channels[BX_MAX_ATA_INTERFACES];

    // ATA devices info
    ata_device_t  devices[BX_MAX_ATA_DEVICES];
    //
    // map between (bios hd id - 0x80) and ata channels
    Bit8u  hdcount, hdidmap[BX_MAX_ATA_DEVICES];                

    // map between (bios cd id - 0xE0) and ata channels
    Bit8u  cdcount, cdidmap[BX_MAX_ATA_DEVICES];                

    // Buffer for DPTE table
    dpte_t dpte;

    // Count of transferred sectors and bytes
    Bit16u trsfsectors;
    Bit32u trsfbytes;

    } ata_t;
  
#if BX_ELTORITO_BOOT
  // ElTorito Device Emulation data 
  typedef struct {
    Bit8u  active;
    Bit8u  media;
    Bit8u  emulated_drive;
    Bit8u  controller_index;
    Bit16u device_spec;
    Bit32u ilba;
    Bit16u buffer_segment;
    Bit16u load_segment;
    Bit16u sector_count;
    
    // Virtual device
    chs_t  vdevice;
    } cdemu_t;
#endif // BX_ELTORITO_BOOT
  
#include "32bitgateway.h"

  // for access to EBDA area
  //     The EBDA structure should conform to 
  //     http://www.cybertrails.com/~fys/rombios.htm document
  //     I made the ata and cdemu structs begin at 0x121 in the EBDA seg
  // EBDA must be at most 768 bytes; it lives at 0x9fc00, and the boot 
  // device tables are at 0x9ff00 -- 0x9ffff
  typedef struct {
    unsigned char filler1[0x3D];

    // FDPT - Can be splitted in data members if needed
    unsigned char fdpt0[0x10];
    unsigned char fdpt1[0x10];

    unsigned char filler2[0xC4];

    // ATA Driver data
    ata_t   ata;

#if BX_ELTORITO_BOOT
    // El Torito Emulation data
    cdemu_t cdemu;
#endif // BX_ELTORITO_BOOT

    upcall_t upcall;
    } ebda_data_t;
  
  #define EbdaData ((ebda_data_t *) 0)

  // for access to the int13ext structure
  typedef struct {
    Bit8u  size;
    Bit8u  reserved;
    Bit16u count;
    Bit16u offset;
    Bit16u segment;
    Bit32u lba1;
    Bit32u lba2;
    } int13ext_t;
 
  #define Int13Ext ((int13ext_t *) 0)

  // Disk Physical Table definition
  typedef struct {
    Bit16u  size;
    Bit16u  infos;
    Bit32u  cylinders;
    Bit32u  heads;
    Bit32u  spt;
    Bit32u  sector_count1;
    Bit32u  sector_count2;
    Bit16u  blksize;
    Bit16u  dpte_segment;
    Bit16u  dpte_offset;
    Bit16u  key;
    Bit8u   dpi_length;
    Bit8u   reserved1;
    Bit16u  reserved2;
    Bit8u   host_bus[4];
    Bit8u   iface_type[8];
    Bit8u   iface_path[8];
    Bit8u   device_path[8];
    Bit8u   reserved3;
    Bit8u   checksum;
    } dpt_t;
 
  #define Int13DPT ((dpt_t *) 0)

#endif // BX_USE_ATADRV

typedef struct {
  union {
    struct {
      Bit16u di, si, bp, sp;
      Bit16u bx, dx, cx, ax;
      } r16;
    struct {
      Bit16u filler[4];
      Bit8u  bl, bh, dl, dh, cl, ch, al, ah;
      } r8;
    } u;
  } pusha_regs_t;

typedef struct {
 union {
  struct {
    Bit32u edi, esi, ebp, esp;
    Bit32u ebx, edx, ecx, eax;
    } r32;
  struct {
    Bit16u di, filler1, si, filler2, bp, filler3, sp, filler4;
    Bit16u bx, filler5, dx, filler6, cx, filler7, ax, filler8;
    } r16;
  struct {
    Bit32u filler[4];
    Bit8u  bl, bh; 
    Bit16u filler1;
    Bit8u  dl, dh; 
    Bit16u filler2;
    Bit8u  cl, ch;
    Bit16u filler3;
    Bit8u  al, ah;
    Bit16u filler4;
    } r8;
  } u;
} pushad_regs_t;

typedef struct {
  union {
    struct {
      Bit16u flags;
      } r16;
    struct {
      Bit8u  flagsl;
      Bit8u  flagsh;
      } r8;
    } u;
  } flags_t;

#define SetCF(x)   x.u.r8.flagsl |= 0x01
#define SetZF(x)   x.u.r8.flagsl |= 0x40
#define ClearCF(x) x.u.r8.flagsl &= 0xfe
#define ClearZF(x) x.u.r8.flagsl &= 0xbf
#define GetCF(x)   (x.u.r8.flagsl & 0x01)

typedef struct {
  Bit16u ip;
  Bit16u cs;
  flags_t flags;
  } iret_addr_t;



static Bit8u          inb();
static Bit8u          inb_cmos();
static void           outb();
static void           outb_cmos();
static Bit16u         inw();
static void           outw();
static void           init_rtc();
static bx_bool        rtc_updating();

static Bit8u          read_byte();
static Bit16u         read_word();
static void           write_byte();
static void           write_word();
static void           bios_printf();
static void           copy_e820_table();

static Bit8u          inhibit_mouse_int_and_events();
static void           enable_mouse_int_and_events();
static Bit8u          send_to_mouse_ctrl();
static Bit8u          get_mouse_data();
static void           set_kbd_command_byte();

static void           int09_function();
static void           int13_harddisk();
static void           int13_cdrom();
static void           int13_cdemu();
static void           int13_eltorito();
static void           int13_diskette_function();
static void           int14_function();
static void           int15_function();
static void           int16_function();
static void           int17_function();
static void           int18_function();
static void           int1a_function();
static void           int70_function();
static void           int74_function();
static Bit16u         get_CS();
//static Bit16u         get_DS();
//static void           set_DS();
static Bit16u         get_SS();
static unsigned int   enqueue_key();
static unsigned int   dequeue_key();
static void           get_hd_geometry();
static void           set_diskette_ret_status();
static void           set_diskette_current_cyl();
static void           determine_floppy_media();
static bx_bool        floppy_drive_exists();
static bx_bool        floppy_drive_recal();
static bx_bool        floppy_media_known();
static bx_bool        floppy_media_sense();
static bx_bool        set_enable_a20();
static void           debugger_on();
static void           debugger_off();
static void           keyboard_init();
static void           keyboard_panic();
static void           shutdown_status_panic();
static void           nmi_handler_msg();

static void           print_bios_banner();
static void           print_boot_device();
static void           print_boot_failure();
static void           print_cdromboot_failure();

# if BX_USE_ATADRV

// ATA / ATAPI driver
void   ata_init();
void   ata_detect();
void   ata_reset();

Bit16u ata_cmd_non_data();
Bit16u ata_cmd_data_in();
Bit16u ata_cmd_data_out();
Bit16u ata_cmd_packet();

Bit16u atapi_get_sense();
Bit16u atapi_is_ready();
Bit16u atapi_is_cdrom();

#endif // BX_USE_ATADRV

#if BX_ELTORITO_BOOT

void   cdemu_init();
Bit8u  cdemu_isactive();
Bit8u  cdemu_emulated_drive();

Bit16u cdrom_boot();

#endif // BX_ELTORITO_BOOT

static char bios_cvs_version_string[] = "$Revision: 1.138 $";
static char bios_date_string[] = "$Date: 2005/05/07 15:55:26 $";

static char CVSID[] = "$Id: rombios.c,v 1.138 2005/05/07 15:55:26 vruppert Exp $";

/* Offset to skip the CVS $Id: prefix */ 
#define bios_version_string  (CVSID + 4)

#define BIOS_PRINTF_HALT     1
#define BIOS_PRINTF_SCREEN   2
#define BIOS_PRINTF_INFO     4
#define BIOS_PRINTF_DEBUG    8
#define BIOS_PRINTF_ALL      (BIOS_PRINTF_SCREEN | BIOS_PRINTF_INFO)
#define BIOS_PRINTF_DEBHALT  (BIOS_PRINTF_SCREEN | BIOS_PRINTF_INFO | BIOS_PRINTF_HALT)

#define printf(format, p...)  bios_printf(BIOS_PRINTF_SCREEN, format, ##p)

// Defines the output macros. 
// BX_DEBUG goes to INFO port until we can easily choose debug info on a 
// per-device basis. Debug info are sent only in debug mode
#if DEBUG_ROMBIOS
#  define BX_DEBUG(format, p...)  bios_printf(BIOS_PRINTF_INFO, format, ##p)    
#else
#  define BX_DEBUG(format, p...) 
#endif
#define BX_INFO(format, p...)   bios_printf(BIOS_PRINTF_INFO, format, ##p)
#define BX_PANIC(format, p...)  bios_printf(BIOS_PRINTF_DEBHALT, format, ##p)

#if DEBUG_ATA
#  define BX_DEBUG_ATA(a...) BX_DEBUG(a)
#else
#  define BX_DEBUG_ATA(a...)
#endif
#if DEBUG_INT13_HD
#  define BX_DEBUG_INT13_HD(a...) BX_DEBUG(a)
#else
#  define BX_DEBUG_INT13_HD(a...)
#endif
#if DEBUG_INT13_CD
#  define BX_DEBUG_INT13_CD(a...) BX_DEBUG(a)
#else
#  define BX_DEBUG_INT13_CD(a...)
#endif
#if DEBUG_INT13_ET
#  define BX_DEBUG_INT13_ET(a...) BX_DEBUG(a)
#else
#  define BX_DEBUG_INT13_ET(a...)
#endif
#if DEBUG_INT13_FL
#  define BX_DEBUG_INT13_FL(a...) BX_DEBUG(a)
#else
#  define BX_DEBUG_INT13_FL(a...)
#endif
#if DEBUG_INT15
#  define BX_DEBUG_INT15(a...) BX_DEBUG(a)
#else
#  define BX_DEBUG_INT15(a...)
#endif
#if DEBUG_INT16
#  define BX_DEBUG_INT16(a...) BX_DEBUG(a)
#else
#  define BX_DEBUG_INT16(a...)
#endif
#if DEBUG_INT1A
#  define BX_DEBUG_INT1A(a...) BX_DEBUG(a)
#else
#  define BX_DEBUG_INT1A(a...)
#endif
#if DEBUG_INT74
#  define BX_DEBUG_INT74(a...) BX_DEBUG(a)
#else
#  define BX_DEBUG_INT74(a...)
#endif

#define SET_AL(val8) AX = ((AX & 0xff00) | (val8))
#define SET_BL(val8) BX = ((BX & 0xff00) | (val8))
#define SET_CL(val8) CX = ((CX & 0xff00) | (val8))
#define SET_DL(val8) DX = ((DX & 0xff00) | (val8))
#define SET_AH(val8) AX = ((AX & 0x00ff) | ((val8) << 8))
#define SET_BH(val8) BX = ((BX & 0x00ff) | ((val8) << 8))
#define SET_CH(val8) CX = ((CX & 0x00ff) | ((val8) << 8))
#define SET_DH(val8) DX = ((DX & 0x00ff) | ((val8) << 8))

#define GET_AL() ( AX & 0x00ff )
#define GET_BL() ( BX & 0x00ff )
#define GET_CL() ( CX & 0x00ff )
#define GET_DL() ( DX & 0x00ff )
#define GET_AH() ( AX >> 8 )
#define GET_BH() ( BX >> 8 )
#define GET_CH() ( CX >> 8 )
#define GET_DH() ( DX >> 8 )

#define GET_ELDL() ( ELDX & 0x00ff )
#define GET_ELDH() ( ELDX >> 8 )

#define SET_CF()     FLAGS |= 0x0001
#define CLEAR_CF()   FLAGS &= 0xfffe
#define GET_CF()     (FLAGS & 0x0001)

#define SET_ZF()     FLAGS |= 0x0040
#define CLEAR_ZF()   FLAGS &= 0xffbf
#define GET_ZF()     (FLAGS & 0x0040)

#define UNSUPPORTED_FUNCTION 0x86

#define none 0
#define MAX_SCAN_CODE 0x53

static struct {
  Bit16u normal;
  Bit16u shift;
  Bit16u control;
  Bit16u alt;
  Bit8u lock_flags;
  } scan_to_scanascii[MAX_SCAN_CODE + 1] = {
      {   none,   none,   none,   none, none },
      { 0x011b, 0x011b, 0x011b, 0x0100, none }, /* escape */
      { 0x0231, 0x0221,   none, 0x7800, none }, /* 1! */
      { 0x0332, 0x0340, 0x0300, 0x7900, none }, /* 2@ */
      { 0x0433, 0x0423,   none, 0x7a00, none }, /* 3# */
      { 0x0534, 0x0524,   none, 0x7b00, none }, /* 4$ */
      { 0x0635, 0x0625,   none, 0x7c00, none }, /* 5% */
      { 0x0736, 0x075e, 0x071e, 0x7d00, none }, /* 6^ */
      { 0x0837, 0x0826,   none, 0x7e00, none }, /* 7& */
      { 0x0938, 0x092a,   none, 0x7f00, none }, /* 8* */
      { 0x0a39, 0x0a28,   none, 0x8000, none }, /* 9( */
      { 0x0b30, 0x0b29,   none, 0x8100, none }, /* 0) */
      { 0x0c2d, 0x0c5f, 0x0c1f, 0x8200, none }, /* -_ */
      { 0x0d3d, 0x0d2b,   none, 0x8300, none }, /* =+ */
      { 0x0e08, 0x0e08, 0x0e7f,   none, none }, /* backspace */
      { 0x0f09, 0x0f00,   none,   none, none }, /* tab */
      { 0x1071, 0x1051, 0x1011, 0x1000, 0x40 }, /* Q */
      { 0x1177, 0x1157, 0x1117, 0x1100, 0x40 }, /* W */
      { 0x1265, 0x1245, 0x1205, 0x1200, 0x40 }, /* E */
      { 0x1372, 0x1352, 0x1312, 0x1300, 0x40 }, /* R */
      { 0x1474, 0x1454, 0x1414, 0x1400, 0x40 }, /* T */
      { 0x1579, 0x1559, 0x1519, 0x1500, 0x40 }, /* Y */
      { 0x1675, 0x1655, 0x1615, 0x1600, 0x40 }, /* U */
      { 0x1769, 0x1749, 0x1709, 0x1700, 0x40 }, /* I */
      { 0x186f, 0x184f, 0x180f, 0x1800, 0x40 }, /* O */
      { 0x1970, 0x1950, 0x1910, 0x1900, 0x40 }, /* P */
      { 0x1a5b, 0x1a7b, 0x1a1b,   none, none }, /* [{ */
      { 0x1b5d, 0x1b7d, 0x1b1d,   none, none }, /* ]} */
      { 0x1c0d, 0x1c0d, 0x1c0a,   none, none }, /* Enter */
      {   none,   none,   none,   none, none }, /* L Ctrl */
      { 0x1e61, 0x1e41, 0x1e01, 0x1e00, 0x40 }, /* A */
      { 0x1f73, 0x1f53, 0x1f13, 0x1f00, 0x40 }, /* S */
      { 0x2064, 0x2044, 0x2004, 0x2000, 0x40 }, /* D */
      { 0x2166, 0x2146, 0x2106, 0x2100, 0x40 }, /* F */
      { 0x2267, 0x2247, 0x2207, 0x2200, 0x40 }, /* G */
      { 0x2368, 0x2348, 0x2308, 0x2300, 0x40 }, /* H */
      { 0x246a, 0x244a, 0x240a, 0x2400, 0x40 }, /* J */
      { 0x256b, 0x254b, 0x250b, 0x2500, 0x40 }, /* K */
      { 0x266c, 0x264c, 0x260c, 0x2600, 0x40 }, /* L */
      { 0x273b, 0x273a,   none,   none, none }, /* ;: */
      { 0x2827, 0x2822,   none,   none, none }, /* '" */
      { 0x2960, 0x297e,   none,   none, none }, /* `~ */
      {   none,   none,   none,   none, none }, /* L shift */
      { 0x2b5c, 0x2b7c, 0x2b1c,   none, none }, /* |\ */
      { 0x2c7a, 0x2c5a, 0x2c1a, 0x2c00, 0x40 }, /* Z */
      { 0x2d78, 0x2d58, 0x2d18, 0x2d00, 0x40 }, /* X */
      { 0x2e63, 0x2e43, 0x2e03, 0x2e00, 0x40 }, /* C */
      { 0x2f76, 0x2f56, 0x2f16, 0x2f00, 0x40 }, /* V */
      { 0x3062, 0x3042, 0x3002, 0x3000, 0x40 }, /* B */
      { 0x316e, 0x314e, 0x310e, 0x3100, 0x40 }, /* N */
      { 0x326d, 0x324d, 0x320d, 0x3200, 0x40 }, /* M */
      { 0x332c, 0x333c,   none,   none, none }, /* ,< */
      { 0x342e, 0x343e,   none,   none, none }, /* .> */
      { 0x352f, 0x353f,   none,   none, none }, /* /? */
      {   none,   none,   none,   none, none }, /* R Shift */
      { 0x372a, 0x372a,   none,   none, none }, /* * */
      {   none,   none,   none,   none, none }, /* L Alt */
      { 0x3920, 0x3920, 0x3920, 0x3920, none }, /* space */
      {   none,   none,   none,   none, none }, /* caps lock */
      { 0x3b00, 0x5400, 0x5e00, 0x6800, none }, /* F1 */
      { 0x3c00, 0x5500, 0x5f00, 0x6900, none }, /* F2 */
      { 0x3d00, 0x5600, 0x6000, 0x6a00, none }, /* F3 */
      { 0x3e00, 0x5700, 0x6100, 0x6b00, none }, /* F4 */
      { 0x3f00, 0x5800, 0x6200, 0x6c00, none }, /* F5 */
      { 0x4000, 0x5900, 0x6300, 0x6d00, none }, /* F6 */
      { 0x4100, 0x5a00, 0x6400, 0x6e00, none }, /* F7 */
      { 0x4200, 0x5b00, 0x6500, 0x6f00, none }, /* F8 */
      { 0x4300, 0x5c00, 0x6600, 0x7000, none }, /* F9 */
      { 0x4400, 0x5d00, 0x6700, 0x7100, none }, /* F10 */
      {   none,   none,   none,   none, none }, /* Num Lock */
      {   none,   none,   none,   none, none }, /* Scroll Lock */
      { 0x4700, 0x4737, 0x7700,   none, 0x20 }, /* 7 Home */
      { 0x4800, 0x4838,   none,   none, 0x20 }, /* 8 UP */
      { 0x4900, 0x4939, 0x8400,   none, 0x20 }, /* 9 PgUp */
      { 0x4a2d, 0x4a2d,   none,   none, none }, /* - */
      { 0x4b00, 0x4b34, 0x7300,   none, 0x20 }, /* 4 Left */
      { 0x4c00, 0x4c35,   none,   none, 0x20 }, /* 5 */
      { 0x4d00, 0x4d36, 0x7400,   none, 0x20 }, /* 6 Right */
      { 0x4e2b, 0x4e2b,   none,   none, none }, /* + */
      { 0x4f00, 0x4f31, 0x7500,   none, 0x20 }, /* 1 End */
      { 0x5000, 0x5032,   none,   none, 0x20 }, /* 2 Down */
      { 0x5100, 0x5133, 0x7600,   none, 0x20 }, /* 3 PgDn */
      { 0x5200, 0x5230,   none,   none, 0x20 }, /* 0 Ins */
      { 0x5300, 0x532e,   none,   none, 0x20 }  /* Del */
      };

  Bit8u
inb(port)
  Bit16u port;
{
ASM_START
  push bp
  mov  bp, sp

    push dx
    mov  dx, 4[bp]
    in   al, dx
    pop  dx

  pop  bp
ASM_END
}

#if BX_USE_ATADRV
  Bit16u
inw(port)
  Bit16u port;
{
ASM_START
  push bp
  mov  bp, sp

    push dx
    mov  dx, 4[bp]
    in   ax, dx
    pop  dx

  pop  bp
ASM_END
}
#endif

  void
outb(port, val)
  Bit16u port;
  Bit8u  val;
{
ASM_START
  push bp
  mov  bp, sp

    push ax
    push dx
    mov  dx, 4[bp]
    mov  al, 6[bp]
    out  dx, al
    pop  dx
    pop  ax

  pop  bp
ASM_END
}

#if BX_USE_ATADRV
  void
outw(port, val)
  Bit16u port;
  Bit16u  val;
{
ASM_START
  push bp
  mov  bp, sp

    push ax
    push dx
    mov  dx, 4[bp]
    mov  ax, 6[bp]
    out  dx, ax
    pop  dx
    pop  ax

  pop  bp
ASM_END
}
#endif

  void
outb_cmos(cmos_reg, val)
  Bit8u cmos_reg;
  Bit8u val;
{
ASM_START
  push bp
  mov  bp, sp

    mov  al, 4[bp] ;; cmos_reg
    out  0x70, al
    mov  al, 6[bp] ;; val
    out  0x71, al

  pop  bp
ASM_END
}

  Bit8u
inb_cmos(cmos_reg)
  Bit8u cmos_reg;
{
ASM_START
  push bp
  mov  bp, sp

    mov  al, 4[bp] ;; cmos_reg
    out 0x70, al
    in  al, 0x71

  pop  bp
ASM_END
}

  void
init_rtc()
{
  outb_cmos(0x0a, 0x26);
  outb_cmos(0x0b, 0x02);
  inb_cmos(0x0c);
  inb_cmos(0x0d);
}

  bx_bool
rtc_updating()
{
  // This function checks to see if the update-in-progress bit
  // is set in CMOS Status Register A.  If not, it returns 0.
  // If it is set, it tries to wait until there is a transition
  // to 0, and will return 0 if such a transition occurs.  A 1
  // is returned only after timing out.  The maximum period
  // that this bit should be set is constrained to 244useconds.
  // The count I use below guarantees coverage or more than
  // this time, with any reasonable IPS setting.

  Bit16u count;

  count = 25000;
  while (--count != 0) {
    if ( (inb_cmos(0x0a) & 0x80) == 0 )
      return(0);
    }
  return(1); // update-in-progress never transitioned to 0
}


  Bit8u
read_byte(seg, offset)
  Bit16u seg;
  Bit16u offset;
{
ASM_START
  push bp
  mov  bp, sp

    push bx
    push ds
    mov  ax, 4[bp] ; segment
    mov  ds, ax
    mov  bx, 6[bp] ; offset
    mov  al, [bx]
    ;; al = return value (byte)
    pop  ds
    pop  bx

  pop  bp
ASM_END
}

  Bit16u
read_word(seg, offset)
  Bit16u seg;
  Bit16u offset;
{
ASM_START
  push bp
  mov  bp, sp

    push bx
    push ds
    mov  ax, 4[bp] ; segment
    mov  ds, ax
    mov  bx, 6[bp] ; offset
    mov  ax, [bx]
    ;; ax = return value (word)
    pop  ds
    pop  bx

  pop  bp
ASM_END
}

  void
write_byte(seg, offset, data)
  Bit16u seg;
  Bit16u offset;
  Bit8u data;
{
ASM_START
  push bp
  mov  bp, sp

    push ax
    push bx
    push ds
    mov  ax, 4[bp] ; segment
    mov  ds, ax
    mov  bx, 6[bp] ; offset
    mov  al, 8[bp] ; data byte
    mov  [bx], al  ; write data byte
    pop  ds
    pop  bx
    pop  ax

  pop  bp
ASM_END
}

  void
write_word(seg, offset, data)
  Bit16u seg;
  Bit16u offset;
  Bit16u data;
{
ASM_START
  push bp
  mov  bp, sp

    push ax
    push bx
    push ds
    mov  ax, 4[bp] ; segment
    mov  ds, ax
    mov  bx, 6[bp] ; offset
    mov  ax, 8[bp] ; data word
    mov  [bx], ax  ; write data word
    pop  ds
    pop  bx
    pop  ax

  pop  bp
ASM_END
}

  Bit16u
get_CS()
{
ASM_START
  mov  ax, cs
ASM_END
}

//  Bit16u
//get_DS()
//{
//ASM_START
//  mov  ax, ds
//ASM_END
//}
//
//  void
//set_DS(ds_selector)
//  Bit16u ds_selector;
//{
//ASM_START
//  push bp
//  mov  bp, sp
//
//    push ax
//    mov  ax, 4[bp] ; ds_selector
//    mov  ds, ax
//    pop  ax
//
//  pop  bp
//ASM_END
//}

  Bit16u
get_SS()
{
ASM_START
  mov  ax, ss
ASM_END
}

#ifdef HVMASSIST
void
copy_e820_table()
{
  Bit8u nr_entries = read_byte(0x9000, 0x1e8);
  Bit32u base_mem;
  if (nr_entries > 32)
  	nr_entries = 32;
  write_word(0xe000, 0x8, nr_entries);
  memcpyb(0xe000, 0x10, 0x9000, 0x2d0, nr_entries * 0x14);
  /* Report the proper base memory size at address 0x0413: otherwise
   * non-e820 code will clobber things if BASE_MEM_IN_K is bigger than
   * the first e820 entry.  Get the size by reading the second 64bit 
   * field of the first e820 slot. */ 
  base_mem = read_dword(0x9000, 0x2d0 + 8);
  write_word(0x40, 0x13, base_mem >> 10);
}
#endif /* HVMASSIST */

#if BX_DEBUG_SERIAL
/* serial debug port*/
#define BX_DEBUG_PORT 0x03f8

/* data */
#define UART_RBR 0x00
#define UART_THR 0x00

/* control */
#define UART_IER 0x01
#define UART_IIR 0x02
#define UART_FCR 0x02
#define UART_LCR 0x03
#define UART_MCR 0x04
#define UART_DLL 0x00
#define UART_DLM 0x01

/* status */
#define UART_LSR 0x05
#define UART_MSR 0x06
#define UART_SCR 0x07

int uart_can_tx_byte(base_port)
    Bit16u base_port;
{
    return inb(base_port + UART_LSR) & 0x20;
}

void uart_wait_to_tx_byte(base_port)
    Bit16u base_port;
{
    while (!uart_can_tx_byte(base_port));
}

void uart_wait_until_sent(base_port)
    Bit16u base_port;
{
    while (!(inb(base_port + UART_LSR) & 0x40));
}

void uart_tx_byte(base_port, data)
    Bit16u base_port;
    Bit8u data;
{
    uart_wait_to_tx_byte(base_port);
    outb(base_port + UART_THR, data);
    uart_wait_until_sent(base_port);
}
#endif

  void
wrch(c)
  Bit8u  c;
{
  ASM_START
  push bp
  mov  bp, sp

  push bx
  mov  ah, #0x0e
  mov  al, 4[bp]
  xor  bx,bx
  int  #0x10
  pop  bx

  pop  bp
  ASM_END
}
 
  void
send(action, c)
  Bit16u action;
  Bit8u  c;
{
#if BX_DEBUG_SERIAL
  if (c == '\n') uart_tx_byte(BX_DEBUG_PORT, '\r');
  uart_tx_byte(BX_DEBUG_PORT, c);
#endif
#ifdef HVMASSIST
  outb(0xE9, c);
#endif
#if BX_VIRTUAL_PORTS
  if (action & BIOS_PRINTF_DEBUG) outb(DEBUG_PORT, c);
  if (action & BIOS_PRINTF_INFO) outb(INFO_PORT, c);
#endif
  if (action & BIOS_PRINTF_SCREEN) {
    if (c == '\n') wrch('\r');
    wrch(c);
  }
}

  void
put_int(action, val, width, neg)
  Bit16u action;
  short val, width;
  bx_bool neg;
{
  short nval = val / 10;
  if (nval)
    put_int(action, nval, width - 1, neg);
  else {
    while (--width > 0) send(action, ' ');
    if (neg) send(action, '-');
  }
  send(action, val - (nval * 10) + '0');
}

  void
put_uint(action, val, width, neg)
  Bit16u action;
  unsigned short val;
  short width;
  bx_bool neg;
{
  unsigned short nval = val / 10;
  if (nval)
    put_uint(action, nval, width - 1, neg);
  else {
    while (--width > 0) send(action, ' ');
    if (neg) send(action, '-');
  }
  send(action, val - (nval * 10) + '0');
}

//--------------------------------------------------------------------------
// bios_printf()
//   A compact variable argument printf function which prints its output via
//   an I/O port so that it can be logged by Bochs/Plex.  
//   Currently, only %x is supported (or %02x, %04x, etc).
//
//   Supports %[format_width][format]
//   where format can be d,x,c,s
//--------------------------------------------------------------------------
  void
bios_printf(action, s)
  Bit16u action;
  Bit8u *s;
{
  Bit8u c, format_char;
  bx_bool  in_format;
  short i;
  Bit16u  *arg_ptr;
  Bit16u   arg_seg, arg, nibble, shift_count, format_width;

  arg_ptr = &s;
  arg_seg = get_SS();

  in_format = 0;
  format_width = 0;

  if ((action & BIOS_PRINTF_DEBHALT) == BIOS_PRINTF_DEBHALT) {
#if BX_VIRTUAL_PORTS
    outb(PANIC_PORT2, 0x00);
#endif
    bios_printf (BIOS_PRINTF_SCREEN, "FATAL: ");
  }

  while (c = read_byte(get_CS(), s)) {
    if ( c == '%' ) {
      in_format = 1;
      format_width = 0;
      }
    else if (in_format) {
      if ( (c>='0') && (c<='9') ) {
        format_width = (format_width * 10) + (c - '0');
        }
      else {
        arg_ptr++; // increment to next arg
        arg = read_word(arg_seg, arg_ptr);
        if (c == 'x') {
          if (format_width == 0)
            format_width = 4;
          for (i=format_width-1; i>=0; i--) {
            nibble = (arg >> (4 * i)) & 0x000f;
            send (action, (nibble<=9)? (nibble+'0') : (nibble-10+'A'));
            }
          }
        else if (c == 'u') {
          put_uint(action, arg, format_width, 0);
          }
        else if (c == 'd') {
          if (arg & 0x8000)
            put_int(action, -arg, format_width - 1, 1);
          else
            put_int(action, arg, format_width, 0);
          }
        else if (c == 's') {
          bios_printf(action & (~BIOS_PRINTF_HALT), arg);
          }
        else if (c == 'c') {
          send(action, arg);
          }
        else
          BX_PANIC("bios_printf: unknown format\n");
          in_format = 0;
        }
      }
    else {
      send(action, c);
      }
    s ++;
    }

  if (action & BIOS_PRINTF_HALT) {
    // freeze in a busy loop.  
ASM_START
    cli
 halt2_loop:
    hlt
    jmp halt2_loop
ASM_END
    }
}

//--------------------------------------------------------------------------
// keyboard_init
//--------------------------------------------------------------------------
// this file is based on LinuxBIOS implementation of keyboard.c
// could convert to #asm to gain space
  void
keyboard_init()
{
    Bit16u max;

    /* ------------------- Flush buffers ------------------------*/
    /* Wait until buffer is empty */
    max=0xffff;
    while ( (inb(0x64) & 0x02) && (--max>0)) outb(0x80, 0x00);

    /* flush incoming keys */
    max=0x2000;
    while (--max > 0) {
        outb(0x80, 0x00);
        if (inb(0x64) & 0x01) {
            inb(0x60);
            max = 0x2000;
            }
        }
  
    // Due to timer issues, and if the IPS setting is > 15000000, 
    // the incoming keys might not be flushed here. That will
    // cause a panic a few lines below.  See sourceforge bug report :
    // [ 642031 ] FATAL: Keyboard RESET error:993

    /* ------------------- controller side ----------------------*/
    /* send cmd = 0xAA, self test 8042 */
    outb(0x64, 0xaa);

    /* Wait until buffer is empty */
    max=0xffff;
    while ( (inb(0x64) & 0x02) && (--max>0)) outb(0x80, 0x00);
    if (max==0x0) keyboard_panic(00);

    /* Wait for data */
    max=0xffff;
    while ( ((inb(0x64) & 0x01) == 0) && (--max>0) ) outb(0x80, 0x01);
    if (max==0x0) keyboard_panic(01);

    /* read self-test result, 0x55 should be returned from 0x60 */
    if ((inb(0x60) != 0x55)){
        keyboard_panic(991);
    }

    /* send cmd = 0xAB, keyboard interface test */
    outb(0x64,0xab);

    /* Wait until buffer is empty */
    max=0xffff;
    while ((inb(0x64) & 0x02) && (--max>0)) outb(0x80, 0x10);
    if (max==0x0) keyboard_panic(10);

    /* Wait for data */
    max=0xffff;
    while ( ((inb(0x64) & 0x01) == 0) && (--max>0) ) outb(0x80, 0x11);
    if (max==0x0) keyboard_panic(11);

    /* read keyboard interface test result, */
    /* 0x00 should be returned form 0x60 */
    if ((inb(0x60) != 0x00)) {
        keyboard_panic(992);
    }

    /* Enable Keyboard clock */
    outb(0x64,0xae);
    outb(0x64,0xa8);

    /* ------------------- keyboard side ------------------------*/
    /* reset kerboard and self test  (keyboard side) */
    outb(0x60, 0xff);

    /* Wait until buffer is empty */
    max=0xffff;
    while ((inb(0x64) & 0x02) && (--max>0)) outb(0x80, 0x20);
    if (max==0x0) keyboard_panic(20);

    /* Wait for data */
    max=0xffff;
    while ( ((inb(0x64) & 0x01) == 0) && (--max>0) ) outb(0x80, 0x21);
    if (max==0x0) keyboard_panic(21);

    /* keyboard should return ACK */
    if ((inb(0x60) != 0xfa)) {
        keyboard_panic(993);
    }

    /* Wait for data */
    max=0xffff;
    while ( ((inb(0x64) & 0x01) == 0) && (--max>0) ) outb(0x80, 0x31);
    if (max==0x0) keyboard_panic(31);

    if ((inb(0x60) != 0xaa)) {
        keyboard_panic(994);
    }

    /* Disable keyboard */
    outb(0x60, 0xf5);

    /* Wait until buffer is empty */
    max=0xffff;
    while ((inb(0x64) & 0x02) && (--max>0)) outb(0x80, 0x40);
    if (max==0x0) keyboard_panic(40);

    /* Wait for data */
    max=0xffff;
    while ( ((inb(0x64) & 0x01) == 0) && (--max>0) ) outb(0x80, 0x41);
    if (max==0x0) keyboard_panic(41);

    /* keyboard should return ACK */
    if ((inb(0x60) != 0xfa)) {
        keyboard_panic(995);
    }

    /* Write Keyboard Mode */
    outb(0x64, 0x60);

    /* Wait until buffer is empty */
    max=0xffff;
    while ((inb(0x64) & 0x02) && (--max>0)) outb(0x80, 0x50);
    if (max==0x0) keyboard_panic(50);

    /* send cmd: scan code convert, disable mouse, enable IRQ 1 */
    outb(0x60, 0x61);

    /* Wait until buffer is empty */
    max=0xffff;
    while ((inb(0x64) & 0x02) && (--max>0)) outb(0x80, 0x60);
    if (max==0x0) keyboard_panic(60);

    /* Enable keyboard */
    outb(0x60, 0xf4);

    /* Wait until buffer is empty */
    max=0xffff;
    while ((inb(0x64) & 0x02) && (--max>0)) outb(0x80, 0x70);
    if (max==0x0) keyboard_panic(70);

    /* Wait for data */
    max=0xffff;
    while ( ((inb(0x64) & 0x01) == 0) && (--max>0) ) outb(0x80, 0x71);
    if (max==0x0) keyboard_panic(70);

    /* keyboard should return ACK */
    if ((inb(0x60) != 0xfa)) {
        keyboard_panic(996);
    }

    outb(0x80, 0x77);
}

//--------------------------------------------------------------------------
// keyboard_panic
//--------------------------------------------------------------------------
  void
keyboard_panic(status)
  Bit16u status;
{
  // If you're getting a 993 keyboard panic here, 
  // please see the comment in keyboard_init
  
  BX_PANIC("Keyboard error:%u\n",status);
}

//--------------------------------------------------------------------------
// machine_reset
//--------------------------------------------------------------------------
  void
machine_reset()
{
  /* Frob the keyboard reset line to reset the processor */
  outb(0x64, 0x60); /* Map the flags register at data port (0x60) */
  outb(0x60, 0x14); /* Set the flags to system|disable */
  outb(0x64, 0xfe); /* Pulse output 0 (system reset) low */
  BX_PANIC("Couldn't reset the machine\n");
}

//--------------------------------------------------------------------------
// clobber_entry_point
//    Because PV drivers in HVM guests detach some of the emulated devices, 
//    it is not safe to do a soft reboot by just dropping to real mode and
//    jumping at ffff:0000. -- the boot drives might have disappeared!
//    This rather foul function overwrites(!) the BIOS entry point 
//    to point at machine-reset, which will cause the Xen tools to
//    rebuild the whole machine from scratch.
//--------------------------------------------------------------------------
  void 
clobber_entry_point() 
{
    /* The instruction at the entry point is one byte (0xea) for the
     * jump opcode, then two bytes of address, then two of segment. 
     * Overwrite the address bytes.*/
    write_word(0xffff, 0x0001, machine_reset); 
}


//--------------------------------------------------------------------------
// shutdown_status_panic
//   called when the shutdown statsu is not implemented, displays the status
//--------------------------------------------------------------------------
  void
shutdown_status_panic(status)
  Bit16u status;
{
  BX_PANIC("Unimplemented shutdown status: %02x\n",(Bit8u)status);
}

//--------------------------------------------------------------------------
// print_bios_banner
//   displays a the bios version
//--------------------------------------------------------------------------
void
print_bios_banner()
{
  printf(BX_APPNAME" BIOS, %d cpu%s, ", BX_SMP_PROCESSORS, BX_SMP_PROCESSORS>1?"s":"");
  printf("%s %s\n", bios_cvs_version_string, bios_date_string);
#if BX_TCGBIOS
  printf("TCG-enabled BIOS.\n");
#endif
  printf("\n");
}


//--------------------------------------------------------------------------
// BIOS Boot Specification 1.0.1 compatibility
//
// Very basic support for the BIOS Boot Specification, which allows expansion 
// ROMs to register themselves as boot devices, instead of just stealing the 
// INT 19h boot vector.
// 
// This is a hack: to do it properly requires a proper PnP BIOS and we aren't
// one; we just lie to the option ROMs to make them behave correctly. 
// We also don't support letting option ROMs register as bootable disk 
// drives (BCVs), only as bootable devices (BEVs). 
//
// http://www.phoenix.com/en/Customer+Services/White+Papers-Specs/pc+industry+specifications.htm
//--------------------------------------------------------------------------

/* 256 bytes at 0x9ff00 -- 0x9ffff is used for the IPL boot table. */
#define IPL_SEG              0x9ff0
#define IPL_TABLE_OFFSET     0x0000
#define IPL_TABLE_ENTRIES    8
#define IPL_COUNT_OFFSET     0x0080  /* u16: number of valid table entries */
#define IPL_SEQUENCE_OFFSET  0x0082  /* u16: next boot device */

struct ipl_entry {
  Bit16u type;
  Bit16u flags;
  Bit32u vector;
  Bit32u description;
  Bit32u reserved;
};

static void 
init_boot_vectors() 
{
  struct ipl_entry e; 
  Bit16u count = 0;
  Bit16u ss = get_SS();

  /* Clear out the IPL table. */
  memsetb(IPL_SEG, IPL_TABLE_OFFSET, 0, 0xff);

  /* Floppy drive */
  e.type = 1; e.flags = 0; e.vector = 0; e.description = 0; e.reserved = 0;
  memcpyb(IPL_SEG, IPL_TABLE_OFFSET + count * sizeof (e), ss, &e, sizeof (e));
  count++;

  /* First HDD */
  e.type = 2; e.flags = 0; e.vector = 0; e.description = 0; e.reserved = 0;
  memcpyb(IPL_SEG, IPL_TABLE_OFFSET + count * sizeof (e), ss, &e, sizeof (e));
  count++;

#if BX_ELTORITO_BOOT
  /* CDROM */
  e.type = 3; e.flags = 0; e.vector = 0; e.description = 0; e.reserved = 0;
  memcpyb(IPL_SEG, IPL_TABLE_OFFSET + count * sizeof (e), ss, &e, sizeof (e));
  count++;
#endif  

  /* Remember how many devices we have */
  write_word(IPL_SEG, IPL_COUNT_OFFSET, count);
  /* Not tried booting anything yet */
  write_word(IPL_SEG, IPL_SEQUENCE_OFFSET, 0xffff);
}

static Bit8u
get_boot_vector(i, e)
Bit16u i; struct ipl_entry *e; 
{
  Bit16u count;
  Bit16u ss = get_SS();
  /* Get the count of boot devices, and refuse to overrun the array */
  count = read_word(IPL_SEG, IPL_COUNT_OFFSET);
  if (i >= count) return 0;
  /* OK to read this device */
  memcpyb(ss, e, IPL_SEG, IPL_TABLE_OFFSET + i * sizeof (*e), sizeof (*e));
  return 1;
}


//--------------------------------------------------------------------------
// print_boot_device
//   displays the boot device
//--------------------------------------------------------------------------

static char drivetypes[][10]={"", "Floppy","Hard Disk","CD-Rom", "Network"};

void
print_boot_device(type)
  Bit16u type;
{
  /* NIC appears as type 0x80 */ 
  if (type == 0x80 ) type = 0x4;
  if (type == 0 || type > 0x4) BX_PANIC("Bad drive type\n"); 
  printf("Booting from %s...\n", drivetypes[type]);
}

//--------------------------------------------------------------------------
// print_boot_failure
//   displays the reason why boot failed
//--------------------------------------------------------------------------
  void
print_boot_failure(type, reason)
  Bit16u type; Bit8u reason;
{
  if (type == 0 || type > 0x3) BX_PANIC("Bad drive type\n"); 

  printf("Boot from %s failed", drivetypes[type]);
  if (type < 4) {
    /* Report the reason too */
  if (reason==0) 
    printf(": not a bootable disk");
  else
    printf(": could not read the boot disk");
  }
  printf("\n");
}

//--------------------------------------------------------------------------
// print_cdromboot_failure
//   displays the reason why boot failed
//--------------------------------------------------------------------------
  void
print_cdromboot_failure( code )
  Bit16u code;
{
  bios_printf(BIOS_PRINTF_SCREEN | BIOS_PRINTF_INFO, "CDROM boot failure code : %04x\n",code);
  
  return;
}

void
nmi_handler_msg()
{
  BX_PANIC("NMI Handler called\n");
}

void
int18_panic_msg()
{
  BX_PANIC("INT18: BOOT FAILURE\n");
}

void
log_bios_start()
{
#if BX_DEBUG_SERIAL
  outb(BX_DEBUG_PORT+UART_LCR, 0x03); /* setup for serial logging: 8N1 */
#endif
  BX_INFO("%s\n", bios_version_string);
}

  bx_bool
set_enable_a20(val)
  bx_bool val;
{
  Bit8u  oldval;

  // Use PS2 System Control port A to set A20 enable

  // get current setting first
  oldval = inb(0x92);

  // change A20 status
  if (val)
    outb(0x92, oldval | 0x02);
  else
    outb(0x92, oldval & 0xfd);

  return((oldval & 0x02) != 0);
}

  void
debugger_on()
{
  outb(0xfedc, 0x01);
}

  void
debugger_off()
{
  outb(0xfedc, 0x00);
}

#if BX_USE_ATADRV

// ---------------------------------------------------------------------------
// Start of ATA/ATAPI Driver
// ---------------------------------------------------------------------------

// Global defines -- ATA register and register bits.
// command block & control block regs
#define ATA_CB_DATA  0   // data reg         in/out pio_base_addr1+0
#define ATA_CB_ERR   1   // error            in     pio_base_addr1+1
#define ATA_CB_FR    1   // feature reg         out pio_base_addr1+1
#define ATA_CB_SC    2   // sector count     in/out pio_base_addr1+2
#define ATA_CB_SN    3   // sector number    in/out pio_base_addr1+3
#define ATA_CB_CL    4   // cylinder low     in/out pio_base_addr1+4
#define ATA_CB_CH    5   // cylinder high    in/out pio_base_addr1+5
#define ATA_CB_DH    6   // device head      in/out pio_base_addr1+6
#define ATA_CB_STAT  7   // primary status   in     pio_base_addr1+7
#define ATA_CB_CMD   7   // command             out pio_base_addr1+7
#define ATA_CB_ASTAT 6   // alternate status in     pio_base_addr2+6
#define ATA_CB_DC    6   // device control      out pio_base_addr2+6
#define ATA_CB_DA    7   // device address   in     pio_base_addr2+7

#define ATA_CB_ER_ICRC 0x80    // ATA Ultra DMA bad CRC
#define ATA_CB_ER_BBK  0x80    // ATA bad block
#define ATA_CB_ER_UNC  0x40    // ATA uncorrected error
#define ATA_CB_ER_MC   0x20    // ATA media change
#define ATA_CB_ER_IDNF 0x10    // ATA id not found
#define ATA_CB_ER_MCR  0x08    // ATA media change request
#define ATA_CB_ER_ABRT 0x04    // ATA command aborted
#define ATA_CB_ER_NTK0 0x02    // ATA track 0 not found
#define ATA_CB_ER_NDAM 0x01    // ATA address mark not found

#define ATA_CB_ER_P_SNSKEY 0xf0   // ATAPI sense key (mask)
#define ATA_CB_ER_P_MCR    0x08   // ATAPI Media Change Request
#define ATA_CB_ER_P_ABRT   0x04   // ATAPI command abort
#define ATA_CB_ER_P_EOM    0x02   // ATAPI End of Media
#define ATA_CB_ER_P_ILI    0x01   // ATAPI Illegal Length Indication

// ATAPI Interrupt Reason bits in the Sector Count reg (CB_SC)
#define ATA_CB_SC_P_TAG    0xf8   // ATAPI tag (mask)
#define ATA_CB_SC_P_REL    0x04   // ATAPI release
#define ATA_CB_SC_P_IO     0x02   // ATAPI I/O
#define ATA_CB_SC_P_CD     0x01   // ATAPI C/D

// bits 7-4 of the device/head (CB_DH) reg
#define ATA_CB_DH_DEV0 0xa0    // select device 0
#define ATA_CB_DH_DEV1 0xb0    // select device 1

// status reg (CB_STAT and CB_ASTAT) bits
#define ATA_CB_STAT_BSY  0x80  // busy
#define ATA_CB_STAT_RDY  0x40  // ready
#define ATA_CB_STAT_DF   0x20  // device fault
#define ATA_CB_STAT_WFT  0x20  // write fault (old name)
#define ATA_CB_STAT_SKC  0x10  // seek complete
#define ATA_CB_STAT_SERV 0x10  // service
#define ATA_CB_STAT_DRQ  0x08  // data request
#define ATA_CB_STAT_CORR 0x04  // corrected
#define ATA_CB_STAT_IDX  0x02  // index
#define ATA_CB_STAT_ERR  0x01  // error (ATA)
#define ATA_CB_STAT_CHK  0x01  // check (ATAPI)

// device control reg (CB_DC) bits
#define ATA_CB_DC_HD15   0x08  // bit should always be set to one
#define ATA_CB_DC_SRST   0x04  // soft reset
#define ATA_CB_DC_NIEN   0x02  // disable interrupts

// Most mandtory and optional ATA commands (from ATA-3),
#define ATA_CMD_CFA_ERASE_SECTORS            0xC0
#define ATA_CMD_CFA_REQUEST_EXT_ERR_CODE     0x03
#define ATA_CMD_CFA_TRANSLATE_SECTOR         0x87
#define ATA_CMD_CFA_WRITE_MULTIPLE_WO_ERASE  0xCD
#define ATA_CMD_CFA_WRITE_SECTORS_WO_ERASE   0x38
#define ATA_CMD_CHECK_POWER_MODE1            0xE5
#define ATA_CMD_CHECK_POWER_MODE2            0x98
#define ATA_CMD_DEVICE_RESET                 0x08
#define ATA_CMD_EXECUTE_DEVICE_DIAGNOSTIC    0x90
#define ATA_CMD_FLUSH_CACHE                  0xE7
#define ATA_CMD_FORMAT_TRACK                 0x50
#define ATA_CMD_IDENTIFY_DEVICE              0xEC
#define ATA_CMD_IDENTIFY_DEVICE_PACKET       0xA1
#define ATA_CMD_IDENTIFY_PACKET_DEVICE       0xA1
#define ATA_CMD_IDLE1                        0xE3
#define ATA_CMD_IDLE2                        0x97
#define ATA_CMD_IDLE_IMMEDIATE1              0xE1
#define ATA_CMD_IDLE_IMMEDIATE2              0x95
#define ATA_CMD_INITIALIZE_DRIVE_PARAMETERS  0x91
#define ATA_CMD_INITIALIZE_DEVICE_PARAMETERS 0x91
#define ATA_CMD_NOP                          0x00
#define ATA_CMD_PACKET                       0xA0
#define ATA_CMD_READ_BUFFER                  0xE4
#define ATA_CMD_READ_DMA                     0xC8
#define ATA_CMD_READ_DMA_QUEUED              0xC7
#define ATA_CMD_READ_MULTIPLE                0xC4
#define ATA_CMD_READ_SECTORS                 0x20
#define ATA_CMD_READ_VERIFY_SECTORS          0x40
#define ATA_CMD_RECALIBRATE                  0x10
#define ATA_CMD_SEEK                         0x70
#define ATA_CMD_SET_FEATURES                 0xEF
#define ATA_CMD_SET_MULTIPLE_MODE            0xC6
#define ATA_CMD_SLEEP1                       0xE6
#define ATA_CMD_SLEEP2                       0x99
#define ATA_CMD_STANDBY1                     0xE2
#define ATA_CMD_STANDBY2                     0x96
#define ATA_CMD_STANDBY_IMMEDIATE1           0xE0
#define ATA_CMD_STANDBY_IMMEDIATE2           0x94
#define ATA_CMD_WRITE_BUFFER                 0xE8
#define ATA_CMD_WRITE_DMA                    0xCA
#define ATA_CMD_WRITE_DMA_QUEUED             0xCC
#define ATA_CMD_WRITE_MULTIPLE               0xC5
#define ATA_CMD_WRITE_SECTORS                0x30
#define ATA_CMD_WRITE_VERIFY                 0x3C

#define ATA_IFACE_NONE    0x00
#define ATA_IFACE_ISA     0x00
#define ATA_IFACE_PCI     0x01

#define ATA_TYPE_NONE     0x00
#define ATA_TYPE_UNKNOWN  0x01
#define ATA_TYPE_ATA      0x02
#define ATA_TYPE_ATAPI    0x03

#define ATA_DEVICE_NONE  0x00
#define ATA_DEVICE_HD    0xFF
#define ATA_DEVICE_CDROM 0x05

#define ATA_MODE_NONE    0x00
#define ATA_MODE_PIO16   0x00
#define ATA_MODE_PIO32   0x01
#define ATA_MODE_ISADMA  0x02
#define ATA_MODE_PCIDMA  0x03
#define ATA_MODE_USEIRQ  0x10

#define ATA_TRANSLATION_NONE  0
#define ATA_TRANSLATION_LBA   1
#define ATA_TRANSLATION_LARGE 2
#define ATA_TRANSLATION_RECHS 3

#define ATA_DATA_NO      0x00
#define ATA_DATA_IN      0x01
#define ATA_DATA_OUT     0x02
  
// ---------------------------------------------------------------------------
// ATA/ATAPI driver : initialization
// ---------------------------------------------------------------------------
void ata_init( )
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit8u  channel, device;

  // Channels info init. 
  for (channel=0; channel<BX_MAX_ATA_INTERFACES; channel++) {
    write_byte(ebda_seg,&EbdaData->ata.channels[channel].iface,ATA_IFACE_NONE);
    write_word(ebda_seg,&EbdaData->ata.channels[channel].iobase1,0x0);
    write_word(ebda_seg,&EbdaData->ata.channels[channel].iobase2,0x0);
    write_byte(ebda_seg,&EbdaData->ata.channels[channel].irq,0);
    }

  // Devices info init. 
  for (device=0; device<BX_MAX_ATA_DEVICES; device++) {
    write_byte(ebda_seg,&EbdaData->ata.devices[device].type,ATA_TYPE_NONE);
    write_byte(ebda_seg,&EbdaData->ata.devices[device].device,ATA_DEVICE_NONE);
    write_byte(ebda_seg,&EbdaData->ata.devices[device].removable,0);
    write_byte(ebda_seg,&EbdaData->ata.devices[device].lock,0);
    write_byte(ebda_seg,&EbdaData->ata.devices[device].mode,ATA_MODE_NONE);
    write_word(ebda_seg,&EbdaData->ata.devices[device].blksize,0);
    write_byte(ebda_seg,&EbdaData->ata.devices[device].translation,ATA_TRANSLATION_NONE);
    write_word(ebda_seg,&EbdaData->ata.devices[device].lchs.heads,0);
    write_word(ebda_seg,&EbdaData->ata.devices[device].lchs.cylinders,0);
    write_word(ebda_seg,&EbdaData->ata.devices[device].lchs.spt,0);
    write_word(ebda_seg,&EbdaData->ata.devices[device].pchs.heads,0);
    write_word(ebda_seg,&EbdaData->ata.devices[device].pchs.cylinders,0);
    write_word(ebda_seg,&EbdaData->ata.devices[device].pchs.spt,0);
    
    write_dword(ebda_seg,&EbdaData->ata.devices[device].sectors,0L);
    }

  // hdidmap  and cdidmap init. 
  for (device=0; device<BX_MAX_ATA_DEVICES; device++) {
    write_byte(ebda_seg,&EbdaData->ata.hdidmap[device],BX_MAX_ATA_DEVICES);
    write_byte(ebda_seg,&EbdaData->ata.cdidmap[device],BX_MAX_ATA_DEVICES);
    }

  write_byte(ebda_seg,&EbdaData->ata.hdcount,0);
  write_byte(ebda_seg,&EbdaData->ata.cdcount,0);
}

// ---------------------------------------------------------------------------
// ATA/ATAPI driver : device detection
// ---------------------------------------------------------------------------

void ata_detect( )
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit8u  hdcount, cdcount, device, type;
  Bit8u  buffer[0x0200];

#if BX_MAX_ATA_INTERFACES > 0
  write_byte(ebda_seg,&EbdaData->ata.channels[0].iface,ATA_IFACE_ISA);
  write_word(ebda_seg,&EbdaData->ata.channels[0].iobase1,0x1f0);
  write_word(ebda_seg,&EbdaData->ata.channels[0].iobase2,0x3f0);
  write_byte(ebda_seg,&EbdaData->ata.channels[0].irq,14);
#endif
#if BX_MAX_ATA_INTERFACES > 1
  write_byte(ebda_seg,&EbdaData->ata.channels[1].iface,ATA_IFACE_ISA);
  write_word(ebda_seg,&EbdaData->ata.channels[1].iobase1,0x170);
  write_word(ebda_seg,&EbdaData->ata.channels[1].iobase2,0x370);
  write_byte(ebda_seg,&EbdaData->ata.channels[1].irq,15);
#endif
#if BX_MAX_ATA_INTERFACES > 2
  write_byte(ebda_seg,&EbdaData->ata.channels[2].iface,ATA_IFACE_ISA);
  write_word(ebda_seg,&EbdaData->ata.channels[2].iobase1,0x1e8);
  write_word(ebda_seg,&EbdaData->ata.channels[2].iobase2,0x3e0);
  write_byte(ebda_seg,&EbdaData->ata.channels[2].irq,12);
#endif
#if BX_MAX_ATA_INTERFACES > 3
  write_byte(ebda_seg,&EbdaData->ata.channels[3].iface,ATA_IFACE_ISA);
  write_word(ebda_seg,&EbdaData->ata.channels[3].iobase1,0x168);
  write_word(ebda_seg,&EbdaData->ata.channels[3].iobase2,0x360);
  write_byte(ebda_seg,&EbdaData->ata.channels[3].irq,11);
#endif
#if BX_MAX_ATA_INTERFACES > 4
#error Please fill the ATA interface informations
#endif

  // Device detection
  hdcount=cdcount=0;
  
  for(device=0; device<BX_MAX_ATA_DEVICES; device++) {
    Bit16u iobase1, iobase2;
    Bit8u  channel, slave, shift;
    Bit8u  sc, sn, cl, ch, st;

    channel = device / 2;
    slave = device % 2;

    iobase1 =read_word(ebda_seg,&EbdaData->ata.channels[channel].iobase1);
    iobase2 =read_word(ebda_seg,&EbdaData->ata.channels[channel].iobase2);

    // Disable interrupts
    outb(iobase2+ATA_CB_DC, ATA_CB_DC_HD15 | ATA_CB_DC_NIEN);

    // Look for device
    outb(iobase1+ATA_CB_DH, slave ? ATA_CB_DH_DEV1 : ATA_CB_DH_DEV0);
    outb(iobase1+ATA_CB_SC, 0x55);
    outb(iobase1+ATA_CB_SN, 0xaa);
    outb(iobase1+ATA_CB_SC, 0xaa);
    outb(iobase1+ATA_CB_SN, 0x55);
    outb(iobase1+ATA_CB_SC, 0x55);
    outb(iobase1+ATA_CB_SN, 0xaa);

    // If we found something
    sc = inb(iobase1+ATA_CB_SC);
    sn = inb(iobase1+ATA_CB_SN);

    if ( (sc == 0x55) && (sn == 0xaa) ) {
      write_byte(ebda_seg,&EbdaData->ata.devices[device].type,ATA_TYPE_UNKNOWN);
    
      // reset the channel
      ata_reset (device);
      
      // check for ATA or ATAPI
      outb(iobase1+ATA_CB_DH, slave ? ATA_CB_DH_DEV1 : ATA_CB_DH_DEV0);
      sc = inb(iobase1+ATA_CB_SC);
      sn = inb(iobase1+ATA_CB_SN);
      if ( (sc==0x01) && (sn==0x01) ) {
        cl = inb(iobase1+ATA_CB_CL);
        ch = inb(iobase1+ATA_CB_CH);
        st = inb(iobase1+ATA_CB_STAT);

        if ( (cl==0x14) && (ch==0xeb) ) {
          write_byte(ebda_seg,&EbdaData->ata.devices[device].type,ATA_TYPE_ATAPI);
          }
        else if ( (cl==0x00) && (ch==0x00) && (st!=0x00) ) {
          write_byte(ebda_seg,&EbdaData->ata.devices[device].type,ATA_TYPE_ATA);
          }
        }
      }

    type=read_byte(ebda_seg,&EbdaData->ata.devices[device].type);
    
    // Now we send a IDENTIFY command to ATA device 
    if(type == ATA_TYPE_ATA) {
      Bit32u sectors;
      Bit16u cylinders, heads, spt, blksize;
      Bit8u  translation, removable, mode;

      // default mode to PIO16
      mode = ATA_MODE_PIO16;

      //Temporary values to do the transfer
      write_byte(ebda_seg,&EbdaData->ata.devices[device].device,ATA_DEVICE_HD);
      write_byte(ebda_seg,&EbdaData->ata.devices[device].mode, ATA_MODE_PIO16);

      if (ata_cmd_data_in(device,ATA_CMD_IDENTIFY_DEVICE, 1, 0, 0, 0, 0L, get_SS(),buffer) !=0 )
        BX_PANIC("ata-detect: Failed to detect ATA device\n");

      removable = (read_byte(get_SS(),buffer+0) & 0x80) ? 1 : 0;
#ifndef	NO_PIO32
      mode      = read_byte(get_SS(),buffer+96) ? ATA_MODE_PIO32 : ATA_MODE_PIO16;
#endif

      blksize   = read_word(get_SS(),buffer+10);
      
      cylinders = read_word(get_SS(),buffer+(1*2)); // word 1
      heads     = read_word(get_SS(),buffer+(3*2)); // word 3
      spt       = read_word(get_SS(),buffer+(6*2)); // word 6

      sectors   = read_dword(get_SS(),buffer+(60*2)); // word 60 and word 61

      write_byte(ebda_seg,&EbdaData->ata.devices[device].device,ATA_DEVICE_HD);
      write_byte(ebda_seg,&EbdaData->ata.devices[device].removable, removable);
      write_byte(ebda_seg,&EbdaData->ata.devices[device].mode, mode);
      write_word(ebda_seg,&EbdaData->ata.devices[device].blksize, blksize);
      write_word(ebda_seg,&EbdaData->ata.devices[device].pchs.heads, heads);
      write_word(ebda_seg,&EbdaData->ata.devices[device].pchs.cylinders, cylinders);
      write_word(ebda_seg,&EbdaData->ata.devices[device].pchs.spt, spt);
      write_dword(ebda_seg,&EbdaData->ata.devices[device].sectors, sectors);
      BX_INFO("ata%d-%d: PCHS=%u/%d/%d translation=", channel, slave,cylinders, heads, spt);

      translation = inb_cmos(0x39 + channel/2);
      for (shift=device%4; shift>0; shift--) translation >>= 2;
      translation &= 0x03;

      write_byte(ebda_seg,&EbdaData->ata.devices[device].translation, translation);

      switch (translation) {
        case ATA_TRANSLATION_NONE:
          BX_INFO("none");
          break;
        case ATA_TRANSLATION_LBA:
          BX_INFO("lba");
          break;
        case ATA_TRANSLATION_LARGE:
          BX_INFO("large");
          break;
        case ATA_TRANSLATION_RECHS:
          BX_INFO("r-echs");
          break;
        }
      switch (translation) {
        case ATA_TRANSLATION_NONE:
          break;
        case ATA_TRANSLATION_LBA:
          spt = 63;
          sectors /= 63;
          heads = sectors / 1024;
          if (heads>128) heads = 255;
          else if (heads>64) heads = 128;
          else if (heads>32) heads = 64;
          else if (heads>16) heads = 32;
          else heads=16;
          cylinders = sectors / heads;
          break;
        case ATA_TRANSLATION_RECHS:
          // Take care not to overflow
          if (heads==16) {
            if(cylinders>61439) cylinders=61439;
            heads=15;
            cylinders = (Bit16u)((Bit32u)(cylinders)*16/15);
            }
          // then go through the large bitshift process
        case ATA_TRANSLATION_LARGE:
          while(cylinders > 1024) {
            cylinders >>= 1;
            heads <<= 1;

            // If we max out the head count
            if (heads > 127) break;
          }
          break;
        }
      // clip to 1024 cylinders in lchs
      if (cylinders > 1024) cylinders=1024;
      BX_INFO(" LCHS=%d/%d/%d\n", cylinders, heads, spt);

      write_word(ebda_seg,&EbdaData->ata.devices[device].lchs.heads, heads);
      write_word(ebda_seg,&EbdaData->ata.devices[device].lchs.cylinders, cylinders);
      write_word(ebda_seg,&EbdaData->ata.devices[device].lchs.spt, spt);
 
      // fill hdidmap 
      write_byte(ebda_seg,&EbdaData->ata.hdidmap[hdcount], device);
      hdcount++;
      }
    
    // Now we send a IDENTIFY command to ATAPI device
    if(type == ATA_TYPE_ATAPI) {
 
      Bit8u  type, removable, mode;
      Bit16u blksize;

      // default mode to PIO16
      mode = ATA_MODE_PIO16;

      //Temporary values to do the transfer
      write_byte(ebda_seg,&EbdaData->ata.devices[device].device,ATA_DEVICE_CDROM);
      write_byte(ebda_seg,&EbdaData->ata.devices[device].mode, ATA_MODE_PIO16);

      if (ata_cmd_data_in(device,ATA_CMD_IDENTIFY_DEVICE_PACKET, 1, 0, 0, 0, 0L, get_SS(),buffer) != 0)
        BX_PANIC("ata-detect: Failed to detect ATAPI device\n");

      type      = read_byte(get_SS(),buffer+1) & 0x1f;
      removable = (read_byte(get_SS(),buffer+0) & 0x80) ? 1 : 0;
#ifndef	NO_PIO32
      mode      = read_byte(get_SS(),buffer+96) ? ATA_MODE_PIO32 : ATA_MODE_PIO16;
#endif
      blksize   = 2048;

      write_byte(ebda_seg,&EbdaData->ata.devices[device].device, type);
      write_byte(ebda_seg,&EbdaData->ata.devices[device].removable, removable);
      write_byte(ebda_seg,&EbdaData->ata.devices[device].mode, mode);
      write_word(ebda_seg,&EbdaData->ata.devices[device].blksize, blksize);

      // fill cdidmap 
      write_byte(ebda_seg,&EbdaData->ata.cdidmap[cdcount], device);
      cdcount++;
      }
  
      {
      Bit32u sizeinmb;
      Bit16u ataversion;
      Bit8u  c, i, version, model[41];
      
      switch (type) {
        case ATA_TYPE_ATA:
          sizeinmb = read_dword(ebda_seg,&EbdaData->ata.devices[device].sectors);
          sizeinmb >>= 11;
        case ATA_TYPE_ATAPI:
          // Read ATA/ATAPI version
          ataversion=((Bit16u)(read_byte(get_SS(),buffer+161))<<8)|read_byte(get_SS(),buffer+160);
          for(version=15;version>0;version--) { 
            if((ataversion&(1<<version))!=0)
            break;
            }

          // Read model name
          for(i=0;i<20;i++){
            write_byte(get_SS(),model+(i*2),read_byte(get_SS(),buffer+(i*2)+54+1));
            write_byte(get_SS(),model+(i*2)+1,read_byte(get_SS(),buffer+(i*2)+54));
            }

          // Reformat
          write_byte(get_SS(),model+40,0x00);
          for(i=39;i>0;i--){
            if(read_byte(get_SS(),model+i)==0x20)
              write_byte(get_SS(),model+i,0x00);
            else break;
            }
          break;
        }

      switch (type) {
        case ATA_TYPE_ATA:
          printf("ata%d %s: ",channel,slave?" slave":"master");
          i=0; while(c=read_byte(get_SS(),model+i++)) printf("%c",c);
          printf(" ATA-%d Hard-Disk (%d MBytes)\n",version,(Bit16u)sizeinmb);
          break;
        case ATA_TYPE_ATAPI:
          printf("ata%d %s: ",channel,slave?" slave":"master");
          i=0; while(c=read_byte(get_SS(),model+i++)) printf("%c",c);
          if(read_byte(ebda_seg,&EbdaData->ata.devices[device].device)==ATA_DEVICE_CDROM)
            printf(" ATAPI-%d CD-Rom/DVD-Rom\n",version);
          else
            printf(" ATAPI-%d Device\n",version);
          break;
        case ATA_TYPE_UNKNOWN:
          printf("ata%d %s: Unknown device\n",channel,slave?" slave":"master");
          break;
        }
      }
    }

  // Store the devices counts
  write_byte(ebda_seg,&EbdaData->ata.hdcount, hdcount);
  write_byte(ebda_seg,&EbdaData->ata.cdcount, cdcount);
  write_byte(0x40,0x75, hdcount);
 
  printf("\n");

  // FIXME : should use bios=cmos|auto|disable bits
  // FIXME : should know about translation bits
  // FIXME : move hard_drive_post here 
  
}

// ---------------------------------------------------------------------------
// ATA/ATAPI driver : software reset 
// ---------------------------------------------------------------------------
// ATA-3
// 8.2.1 Software reset - Device 0

void   ata_reset(device)
Bit16u device;
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit16u iobase1, iobase2;
  Bit8u  channel, slave, sn, sc; 
  Bit16u max;

  channel = device / 2;
  slave = device % 2;

  iobase1 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase1);
  iobase2 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase2);

  // Reset

// 8.2.1 (a) -- set SRST in DC
  outb(iobase2+ATA_CB_DC, ATA_CB_DC_HD15 | ATA_CB_DC_NIEN | ATA_CB_DC_SRST);

// 8.2.1 (b) -- wait for BSY
  max=0xff;
  while(--max>0) {
    Bit8u status = inb(iobase1+ATA_CB_STAT);
    if ((status & ATA_CB_STAT_BSY) != 0) break;
  }

// 8.2.1 (f) -- clear SRST
  outb(iobase2+ATA_CB_DC, ATA_CB_DC_HD15 | ATA_CB_DC_NIEN);

  if (read_byte(ebda_seg,&EbdaData->ata.devices[device].type) != ATA_TYPE_NONE) {

// 8.2.1 (g) -- check for sc==sn==0x01
    // select device
    outb(iobase1+ATA_CB_DH, slave?ATA_CB_DH_DEV1:ATA_CB_DH_DEV0);
    sc = inb(iobase1+ATA_CB_SC);
    sn = inb(iobase1+ATA_CB_SN);

    if ( (sc==0x01) && (sn==0x01) ) {

// 8.2.1 (h) -- wait for not BSY
      max=0xff;
      while(--max>0) {
        Bit8u status = inb(iobase1+ATA_CB_STAT);
        if ((status & ATA_CB_STAT_BSY) == 0) break;
        }
      }
    }

// 8.2.1 (i) -- wait for DRDY
  max=0xfff;
  while(--max>0) {
    Bit8u status = inb(iobase1+ATA_CB_STAT);
      if ((status & ATA_CB_STAT_RDY) != 0) break;
  }

  // Enable interrupts
  outb(iobase2+ATA_CB_DC, ATA_CB_DC_HD15);
}

// ---------------------------------------------------------------------------
// ATA/ATAPI driver : execute a non data command 
// ---------------------------------------------------------------------------

Bit16u ata_cmd_non_data()
{return 0;}

// ---------------------------------------------------------------------------
// ATA/ATAPI driver : execute a data-in command
// ---------------------------------------------------------------------------
      // returns
      // 0 : no error
      // 1 : BUSY bit set
      // 2 : read error
      // 3 : expected DRQ=1
      // 4 : no sectors left to read/verify
      // 5 : more sectors to read/verify
      // 6 : no sectors left to write
      // 7 : more sectors to write
Bit16u ata_cmd_data_in(device, command, count, cylinder, head, sector, lba, segment, offset)
Bit16u device, command, count, cylinder, head, sector, segment, offset;
Bit32u lba;
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit16u iobase1, iobase2, blksize;
  Bit8u  channel, slave;
  Bit8u  status, current, mode;

  channel = device / 2;
  slave   = device % 2;

  iobase1 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase1);
  iobase2 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase2);
  mode    = read_byte(ebda_seg, &EbdaData->ata.devices[device].mode);
  blksize = 0x200; // was = read_word(ebda_seg, &EbdaData->ata.devices[device].blksize);
  if (mode == ATA_MODE_PIO32) blksize>>=2;
  else blksize>>=1;

  // sector will be 0 only on lba access. Convert to lba-chs
  if (sector == 0) {
    sector = (Bit16u) (lba & 0x000000ffL);
    lba >>= 8;
    cylinder = (Bit16u) (lba & 0x0000ffffL);
    lba >>= 16;
    head = ((Bit16u) (lba & 0x0000000fL)) | 0x40;
    }

  // Reset count of transferred data
  write_word(ebda_seg, &EbdaData->ata.trsfsectors,0);
  write_dword(ebda_seg, &EbdaData->ata.trsfbytes,0L);
  current = 0;

  status = inb(iobase1 + ATA_CB_STAT);
  if (status & ATA_CB_STAT_BSY) return 1;

  outb(iobase2 + ATA_CB_DC, ATA_CB_DC_HD15 | ATA_CB_DC_NIEN);
  outb(iobase1 + ATA_CB_FR, 0x00);
  outb(iobase1 + ATA_CB_SC, count);
  outb(iobase1 + ATA_CB_SN, sector);
  outb(iobase1 + ATA_CB_CL, cylinder & 0x00ff);
  outb(iobase1 + ATA_CB_CH, cylinder >> 8);
  outb(iobase1 + ATA_CB_DH, (slave ? ATA_CB_DH_DEV1 : ATA_CB_DH_DEV0) | (Bit8u) head );
  outb(iobase1 + ATA_CB_CMD, command);

  while (1) {
    status = inb(iobase1 + ATA_CB_STAT);
    if ( !(status & ATA_CB_STAT_BSY) ) break;
    }

  if (status & ATA_CB_STAT_ERR) {
    BX_DEBUG_ATA("ata_cmd_data_in : read error\n");
    return 2;
    } else if ( !(status & ATA_CB_STAT_DRQ) ) {
    BX_DEBUG_ATA("ata_cmd_data_in : DRQ not set (status %02x)\n", (unsigned) status);
    return 3;
  }

  // FIXME : move seg/off translation here

ASM_START
        sti  ;; enable higher priority interrupts
ASM_END

  while (1) {

ASM_START
        push bp
        mov  bp, sp
        mov  di, _ata_cmd_data_in.offset + 2[bp]  
        mov  ax, _ata_cmd_data_in.segment + 2[bp] 
        mov  cx, _ata_cmd_data_in.blksize + 2[bp] 

        ;; adjust if there will be an overrun. 2K max sector size
        cmp   di, #0xf800 ;; 
        jbe   ata_in_no_adjust

ata_in_adjust:
        sub   di, #0x0800 ;; sub 2 kbytes from offset
        add   ax, #0x0080 ;; add 2 Kbytes to segment

ata_in_no_adjust:
        mov   es, ax      ;; segment in es

        mov   dx, _ata_cmd_data_in.iobase1 + 2[bp] ;; ATA data read port

        mov  ah, _ata_cmd_data_in.mode + 2[bp] 
        cmp  ah, #ATA_MODE_PIO32
        je   ata_in_32

ata_in_16:
        rep
          insw ;; CX words transfered from port(DX) to ES:[DI]
        jmp ata_in_done

ata_in_32:
        rep
          insd ;; CX dwords transfered from port(DX) to ES:[DI]

ata_in_done:
        mov  _ata_cmd_data_in.offset + 2[bp], di
        mov  _ata_cmd_data_in.segment + 2[bp], es
        pop  bp
ASM_END

    current++;
    write_word(ebda_seg, &EbdaData->ata.trsfsectors,current);
    count--;
    status = inb(iobase1 + ATA_CB_STAT);
    if (count == 0) {
      if ( (status & (ATA_CB_STAT_BSY | ATA_CB_STAT_RDY | ATA_CB_STAT_DRQ | ATA_CB_STAT_ERR) ) 
          != ATA_CB_STAT_RDY ) {
        BX_DEBUG_ATA("ata_cmd_data_in : no sectors left (status %02x)\n", (unsigned) status);
        return 4;
        }
      break;
      }
    else {
      if ( (status & (ATA_CB_STAT_BSY | ATA_CB_STAT_RDY | ATA_CB_STAT_DRQ | ATA_CB_STAT_ERR) ) 
          != (ATA_CB_STAT_RDY | ATA_CB_STAT_DRQ) ) {
        BX_DEBUG_ATA("ata_cmd_data_in : more sectors left (status %02x)\n", (unsigned) status);
        return 5;
      }
      continue;
    }
  }
  // Enable interrupts
  outb(iobase2+ATA_CB_DC, ATA_CB_DC_HD15);
  return 0;
}

// ---------------------------------------------------------------------------
// ATA/ATAPI driver : execute a data-out command
// ---------------------------------------------------------------------------
      // returns
      // 0 : no error
      // 1 : BUSY bit set
      // 2 : read error
      // 3 : expected DRQ=1
      // 4 : no sectors left to read/verify
      // 5 : more sectors to read/verify
      // 6 : no sectors left to write
      // 7 : more sectors to write
Bit16u ata_cmd_data_out(device, command, count, cylinder, head, sector, lba, segment, offset)
Bit16u device, command, count, cylinder, head, sector, segment, offset;
Bit32u lba;
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit16u iobase1, iobase2, blksize;
  Bit8u  channel, slave;
  Bit8u  status, current, mode;

  channel = device / 2;
  slave   = device % 2;

  iobase1 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase1);
  iobase2 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase2);
  mode    = read_byte(ebda_seg, &EbdaData->ata.devices[device].mode);
  blksize = 0x200; // was = read_word(ebda_seg, &EbdaData->ata.devices[device].blksize);
  if (mode == ATA_MODE_PIO32) blksize>>=2;
  else blksize>>=1;

  // sector will be 0 only on lba access. Convert to lba-chs
  if (sector == 0) {
    sector = (Bit16u) (lba & 0x000000ffL);
    lba >>= 8;
    cylinder = (Bit16u) (lba & 0x0000ffffL);
    lba >>= 16;
    head = ((Bit16u) (lba & 0x0000000fL)) | 0x40;
    }

  // Reset count of transferred data
  write_word(ebda_seg, &EbdaData->ata.trsfsectors,0);
  write_dword(ebda_seg, &EbdaData->ata.trsfbytes,0L);
  current = 0;

  status = inb(iobase1 + ATA_CB_STAT);
  if (status & ATA_CB_STAT_BSY) return 1;

  outb(iobase2 + ATA_CB_DC, ATA_CB_DC_HD15 | ATA_CB_DC_NIEN);
  outb(iobase1 + ATA_CB_FR, 0x00);
  outb(iobase1 + ATA_CB_SC, count);
  outb(iobase1 + ATA_CB_SN, sector);
  outb(iobase1 + ATA_CB_CL, cylinder & 0x00ff);
  outb(iobase1 + ATA_CB_CH, cylinder >> 8);
  outb(iobase1 + ATA_CB_DH, (slave ? ATA_CB_DH_DEV1 : ATA_CB_DH_DEV0) | (Bit8u) head );
  outb(iobase1 + ATA_CB_CMD, command);

  while (1) {
    status = inb(iobase1 + ATA_CB_STAT);
    if ( !(status & ATA_CB_STAT_BSY) ) break;
    }

  if (status & ATA_CB_STAT_ERR) {
    BX_DEBUG_ATA("ata_cmd_data_out : read error\n");
    return 2;
    } else if ( !(status & ATA_CB_STAT_DRQ) ) {
    BX_DEBUG_ATA("ata_cmd_data_out : DRQ not set (status %02x)\n", (unsigned) status);
    return 3;
    }

  // FIXME : move seg/off translation here

ASM_START
        sti  ;; enable higher priority interrupts
ASM_END

  while (1) {

ASM_START
        push bp
        mov  bp, sp
        mov  si, _ata_cmd_data_out.offset + 2[bp]  
        mov  ax, _ata_cmd_data_out.segment + 2[bp] 
        mov  cx, _ata_cmd_data_out.blksize + 2[bp] 

        ;; adjust if there will be an overrun. 2K max sector size
        cmp   si, #0xf800 ;; 
        jbe   ata_out_no_adjust

ata_out_adjust:
        sub   si, #0x0800 ;; sub 2 kbytes from offset
        add   ax, #0x0080 ;; add 2 Kbytes to segment

ata_out_no_adjust:
        mov   es, ax      ;; segment in es

        mov   dx, _ata_cmd_data_out.iobase1 + 2[bp] ;; ATA data write port

        mov  ah, _ata_cmd_data_out.mode + 2[bp] 
        cmp  ah, #ATA_MODE_PIO32
        je   ata_out_32

ata_out_16:
        seg ES
        rep
          outsw ;; CX words transfered from port(DX) to ES:[SI]
        jmp ata_out_done

ata_out_32:
        seg ES
        rep
          outsd ;; CX dwords transfered from port(DX) to ES:[SI]

ata_out_done:
        mov  _ata_cmd_data_out.offset + 2[bp], si
        mov  _ata_cmd_data_out.segment + 2[bp], es
        pop  bp
ASM_END

    current++;
    write_word(ebda_seg, &EbdaData->ata.trsfsectors,current);
    count--;
    status = inb(iobase1 + ATA_CB_STAT);
    if (count == 0) {
      if ( (status & (ATA_CB_STAT_BSY | ATA_CB_STAT_RDY | ATA_CB_STAT_DF | ATA_CB_STAT_DRQ | ATA_CB_STAT_ERR) ) 
          != ATA_CB_STAT_RDY ) {
        BX_DEBUG_ATA("ata_cmd_data_out : no sectors left (status %02x)\n", (unsigned) status);
        return 6;
        }
      break;
      }
    else {
      if ( (status & (ATA_CB_STAT_BSY | ATA_CB_STAT_RDY | ATA_CB_STAT_DRQ | ATA_CB_STAT_ERR) ) 
          != (ATA_CB_STAT_RDY | ATA_CB_STAT_DRQ) ) {
        BX_DEBUG_ATA("ata_cmd_data_out : more sectors left (status %02x)\n", (unsigned) status);
        return 7;
      }
      continue;
    }
  }
  // Enable interrupts
  outb(iobase2+ATA_CB_DC, ATA_CB_DC_HD15);
  return 0;
}

// ---------------------------------------------------------------------------
// ATA/ATAPI driver : execute a packet command
// ---------------------------------------------------------------------------
      // returns
      // 0 : no error
      // 1 : error in parameters
      // 2 : BUSY bit set
      // 3 : error
      // 4 : not ready
Bit16u ata_cmd_packet(device, cmdlen, cmdseg, cmdoff, header, length, inout, bufseg, bufoff)
Bit8u  cmdlen,inout;
Bit16u device,cmdseg, cmdoff, bufseg, bufoff;
Bit16u header;
Bit32u length;
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit16u iobase1, iobase2;
  Bit16u lcount, lbefore, lafter, count;
  Bit8u  channel, slave;
  Bit8u  status, mode, lmode;
  Bit32u total, transfer;

  channel = device / 2;
  slave = device % 2;

  // Data out is not supported yet
  if (inout == ATA_DATA_OUT) {
    BX_INFO("ata_cmd_packet: DATA_OUT not supported yet\n");
    return 1;
    }

  // The header length must be even
  if (header & 1) {
    BX_DEBUG_ATA("ata_cmd_packet : header must be even (%04x)\n",header);
    return 1;
    }

  iobase1 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase1);
  iobase2 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase2);
  mode    = read_byte(ebda_seg, &EbdaData->ata.devices[device].mode);
  transfer= 0L;

  if (cmdlen < 12) cmdlen=12;
  if (cmdlen > 12) cmdlen=16;
  cmdlen>>=1;

  // Reset count of transferred data
  write_word(ebda_seg, &EbdaData->ata.trsfsectors,0);
  write_dword(ebda_seg, &EbdaData->ata.trsfbytes,0L);

  status = inb(iobase1 + ATA_CB_STAT);
  if (status & ATA_CB_STAT_BSY) return 2;

  outb(iobase2 + ATA_CB_DC, ATA_CB_DC_HD15 | ATA_CB_DC_NIEN);
  // outb(iobase1 + ATA_CB_FR, 0x00);
  // outb(iobase1 + ATA_CB_SC, 0x00);
  // outb(iobase1 + ATA_CB_SN, 0x00);
  outb(iobase1 + ATA_CB_CL, 0xfff0 & 0x00ff);
  outb(iobase1 + ATA_CB_CH, 0xfff0 >> 8);
  outb(iobase1 + ATA_CB_DH, slave ? ATA_CB_DH_DEV1 : ATA_CB_DH_DEV0);
  outb(iobase1 + ATA_CB_CMD, ATA_CMD_PACKET);

  // Device should ok to receive command
  while (1) {
    status = inb(iobase1 + ATA_CB_STAT);
    if ( !(status & ATA_CB_STAT_BSY) ) break;
    }

  if (status & ATA_CB_STAT_ERR) {
    BX_DEBUG_ATA("ata_cmd_packet : error, status is %02x\n",status);
    return 3;
    } else if ( !(status & ATA_CB_STAT_DRQ) ) {
    BX_DEBUG_ATA("ata_cmd_packet : DRQ not set (status %02x)\n", (unsigned) status);
    return 4;
    }

  // Normalize address
  cmdseg += (cmdoff / 16);
  cmdoff %= 16;

  // Send command to device
ASM_START
      sti  ;; enable higher priority interrupts
 
      push bp
      mov  bp, sp
    
      mov  si, _ata_cmd_packet.cmdoff + 2[bp]  
      mov  ax, _ata_cmd_packet.cmdseg + 2[bp] 
      mov  cx, _ata_cmd_packet.cmdlen + 2[bp] 
      mov  es, ax      ;; segment in es

      mov  dx, _ata_cmd_packet.iobase1 + 2[bp] ;; ATA data write port

      seg ES
      rep
        outsw ;; CX words transfered from port(DX) to ES:[SI]

      pop  bp
ASM_END

  if (inout == ATA_DATA_NO) {
    status = inb(iobase1 + ATA_CB_STAT);
    }
  else {
  while (1) {

      status = inb(iobase1 + ATA_CB_STAT);

      // Check if command completed
      if ( (status & (ATA_CB_STAT_BSY | ATA_CB_STAT_DRQ) ) ==0 ) break;

      if (status & ATA_CB_STAT_ERR) {
        BX_DEBUG_ATA("ata_cmd_packet : error (status %02x)\n",status);
        return 3;
      }

      // Device must be ready to send data
      if ( (status & (ATA_CB_STAT_BSY | ATA_CB_STAT_RDY | ATA_CB_STAT_DRQ | ATA_CB_STAT_ERR) ) 
            != (ATA_CB_STAT_RDY | ATA_CB_STAT_DRQ) ) {
        BX_DEBUG_ATA("ata_cmd_packet : not ready (status %02x)\n", status);
        return 4;
        }

      // Normalize address
      bufseg += (bufoff / 16);
      bufoff %= 16;
    
      // Get the byte count
      lcount =  ((Bit16u)(inb(iobase1 + ATA_CB_CH))<<8)+inb(iobase1 + ATA_CB_CL);

      // adjust to read what we want
      if(header>lcount) {
         lbefore=lcount;
         header-=lcount;
         lcount=0;
         }
      else {
        lbefore=header;
        header=0;
        lcount-=lbefore;
        }

      if(lcount>length) {
        lafter=lcount-length;
        lcount=length;
        length=0;
        }
      else {
        lafter=0;
        length-=lcount;
        }

      // Save byte count
      count = lcount;

      BX_DEBUG_ATA("Trying to read %04x bytes (%04x %04x %04x) ",lbefore+lcount+lafter,lbefore,lcount,lafter);
      BX_DEBUG_ATA("to 0x%04x:0x%04x\n",bufseg,bufoff);

      // If counts not dividable by 4, use 16bits mode
      lmode = mode;
      if (lbefore & 0x03) lmode=ATA_MODE_PIO16;
      if (lcount  & 0x03) lmode=ATA_MODE_PIO16;
      if (lafter  & 0x03) lmode=ATA_MODE_PIO16;

      // adds an extra byte if count are odd. before is always even
      if (lcount & 0x01) {
        lcount+=1;
        if ((lafter > 0) && (lafter & 0x01)) {
          lafter-=1;
          }
        }

      if (lmode == ATA_MODE_PIO32) {
        lcount>>=2; lbefore>>=2; lafter>>=2;
        }
      else {
        lcount>>=1; lbefore>>=1; lafter>>=1;
        }

       ;  // FIXME bcc bug

ASM_START
        push bp
        mov  bp, sp

        mov  dx, _ata_cmd_packet.iobase1 + 2[bp] ;; ATA data read port

        mov  cx, _ata_cmd_packet.lbefore + 2[bp] 
        jcxz ata_packet_no_before

        mov  ah, _ata_cmd_packet.lmode + 2[bp] 
        cmp  ah, #ATA_MODE_PIO32
        je   ata_packet_in_before_32

ata_packet_in_before_16:
        in   ax, dx
        loop ata_packet_in_before_16
        jmp  ata_packet_no_before

ata_packet_in_before_32:
        push eax
ata_packet_in_before_32_loop:
        in   eax, dx
        loop ata_packet_in_before_32_loop
        pop  eax

ata_packet_no_before:
        mov  cx, _ata_cmd_packet.lcount + 2[bp] 
        jcxz ata_packet_after

        mov  di, _ata_cmd_packet.bufoff + 2[bp]  
        mov  ax, _ata_cmd_packet.bufseg + 2[bp] 
        mov  es, ax

        mov  ah, _ata_cmd_packet.lmode + 2[bp] 
        cmp  ah, #ATA_MODE_PIO32
        je   ata_packet_in_32

ata_packet_in_16:
        rep
          insw ;; CX words transfered tp port(DX) to ES:[DI]
        jmp ata_packet_after

ata_packet_in_32:
        rep
          insd ;; CX dwords transfered to port(DX) to ES:[DI]

ata_packet_after:
        mov  cx, _ata_cmd_packet.lafter + 2[bp] 
        jcxz ata_packet_done

        mov  ah, _ata_cmd_packet.lmode + 2[bp] 
        cmp  ah, #ATA_MODE_PIO32
        je   ata_packet_in_after_32

ata_packet_in_after_16:
        in   ax, dx
        loop ata_packet_in_after_16
        jmp  ata_packet_done

ata_packet_in_after_32:
        push eax
ata_packet_in_after_32_loop:
        in   eax, dx
        loop ata_packet_in_after_32_loop
        pop  eax

ata_packet_done:
        pop  bp
ASM_END

      // Compute new buffer address
      bufoff += count;

      // Save transferred bytes count
      transfer += count;
      write_dword(ebda_seg, &EbdaData->ata.trsfbytes,transfer);
      }
    }

  // Final check, device must be ready
  if ( (status & (ATA_CB_STAT_BSY | ATA_CB_STAT_RDY | ATA_CB_STAT_DF | ATA_CB_STAT_DRQ | ATA_CB_STAT_ERR) ) 
         != ATA_CB_STAT_RDY ) {
    BX_DEBUG_ATA("ata_cmd_packet : not ready (status %02x)\n", (unsigned) status);
    return 4;
    }

  // Enable interrupts
  outb(iobase2+ATA_CB_DC, ATA_CB_DC_HD15);
  return 0;
}

// ---------------------------------------------------------------------------
// End of ATA/ATAPI Driver
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// Start of ATA/ATAPI generic functions
// ---------------------------------------------------------------------------

  Bit16u 
atapi_get_sense(device)
  Bit16u device;
{
  Bit8u  atacmd[12];
  Bit8u  buffer[16];
  Bit8u i;

  memsetb(get_SS(),atacmd,0,12);

  // Request SENSE 
  atacmd[0]=0x03;    
  atacmd[4]=0x20;    
  if (ata_cmd_packet(device, 12, get_SS(), atacmd, 0, 16L, ATA_DATA_IN, get_SS(), buffer) != 0)
    return 0x0002;

  if ((buffer[0] & 0x7e) == 0x70) {
    return (((Bit16u)buffer[2]&0x0f)*0x100)+buffer[12];
    }

  return 0;
}

  Bit16u 
atapi_is_ready(device)
  Bit16u device;
{
  Bit8u  atacmd[12];
  Bit8u  buffer[];

  memsetb(get_SS(),atacmd,0,12);
 
  // Test Unit Ready
  if (ata_cmd_packet(device, 12, get_SS(), atacmd, 0, 0L, ATA_DATA_NO, get_SS(), buffer) != 0)
    return 0x000f;

  if (atapi_get_sense(device) !=0 ) {
    memsetb(get_SS(),atacmd,0,12);

    // try to send Test Unit Ready again
    if (ata_cmd_packet(device, 12, get_SS(), atacmd, 0, 0L, ATA_DATA_NO, get_SS(), buffer) != 0)
      return 0x000f;

    return atapi_get_sense(device);
    }
  return 0;
}

  Bit16u 
atapi_is_cdrom(device)
  Bit8u device;
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);

  if (device >= BX_MAX_ATA_DEVICES)
    return 0;

  if (read_byte(ebda_seg,&EbdaData->ata.devices[device].type) != ATA_TYPE_ATAPI)
    return 0;

  if (read_byte(ebda_seg,&EbdaData->ata.devices[device].device) != ATA_DEVICE_CDROM)
    return 0;

  return 1;
}

// ---------------------------------------------------------------------------
// End of ATA/ATAPI generic functions
// ---------------------------------------------------------------------------

#endif // BX_USE_ATADRV

#if BX_ELTORITO_BOOT

// ---------------------------------------------------------------------------
// Start of El-Torito boot functions
// ---------------------------------------------------------------------------

  void
cdemu_init()
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);

  // the only important data is this one for now
  write_byte(ebda_seg,&EbdaData->cdemu.active,0x00);
}

  Bit8u
cdemu_isactive()
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);

  return(read_byte(ebda_seg,&EbdaData->cdemu.active));
}

  Bit8u
cdemu_emulated_drive()
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);

  return(read_byte(ebda_seg,&EbdaData->cdemu.emulated_drive));
}

static char isotag[6]="CD001";
static char eltorito[24]="EL TORITO SPECIFICATION";
//
// Returns ah: emulated drive, al: error code
//
  Bit16u 
cdrom_boot()
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit8u  atacmd[12], buffer[2048];
  Bit32u lba;
  Bit16u boot_segment, nbsectors, i, error;
  Bit8u  device;

  // Find out the first cdrom
  for (device=0; device<BX_MAX_ATA_DEVICES;device++) {
    if (atapi_is_cdrom(device)) break;
    }
  
  // if not found
  if(device >= BX_MAX_ATA_DEVICES) return 2;

  // Read the Boot Record Volume Descriptor
  memsetb(get_SS(),atacmd,0,12);
  atacmd[0]=0x28;                      // READ command
  atacmd[7]=(0x01 & 0xff00) >> 8;      // Sectors
  atacmd[8]=(0x01 & 0x00ff);           // Sectors
  atacmd[2]=(0x11 & 0xff000000) >> 24; // LBA
  atacmd[3]=(0x11 & 0x00ff0000) >> 16;
  atacmd[4]=(0x11 & 0x0000ff00) >> 8;
  atacmd[5]=(0x11 & 0x000000ff);
  if((error = ata_cmd_packet(device, 12, get_SS(), atacmd, 0, 2048L, ATA_DATA_IN, get_SS(), buffer)) != 0)
    return 3;

  // Validity checks
  if(buffer[0]!=0)return 4;
  for(i=0;i<5;i++){
    if(buffer[1+i]!=read_byte(0xf000,&isotag[i]))return 5;
   }
  for(i=0;i<23;i++)
    if(buffer[7+i]!=read_byte(0xf000,&eltorito[i]))return 6;
  
  // ok, now we calculate the Boot catalog address
  lba=buffer[0x4A]*0x1000000+buffer[0x49]*0x10000+buffer[0x48]*0x100+buffer[0x47];

  // And we read the Boot Catalog
  memsetb(get_SS(),atacmd,0,12);
  atacmd[0]=0x28;                      // READ command
  atacmd[7]=(0x01 & 0xff00) >> 8;      // Sectors
  atacmd[8]=(0x01 & 0x00ff);           // Sectors
  atacmd[2]=(lba & 0xff000000) >> 24;  // LBA
  atacmd[3]=(lba & 0x00ff0000) >> 16;
  atacmd[4]=(lba & 0x0000ff00) >> 8;
  atacmd[5]=(lba & 0x000000ff);
  if((error = ata_cmd_packet(device, 12, get_SS(), atacmd, 0, 2048L, ATA_DATA_IN, get_SS(), buffer)) != 0)
    return 7;
 
  // Validation entry
  if(buffer[0x00]!=0x01)return 8;   // Header
  if(buffer[0x01]!=0x00)return 9;   // Platform
  if(buffer[0x1E]!=0x55)return 10;  // key 1
  if(buffer[0x1F]!=0xAA)return 10;  // key 2

  // Initial/Default Entry
  if(buffer[0x20]!=0x88)return 11; // Bootable

  write_byte(ebda_seg,&EbdaData->cdemu.media,buffer[0x21]);
  if(buffer[0x21]==0){
    // FIXME ElTorito Hardcoded. cdrom is hardcoded as device 0xE0. 
    // Win2000 cd boot needs to know it booted from cd
    write_byte(ebda_seg,&EbdaData->cdemu.emulated_drive,0xE0);
    } 
  else if(buffer[0x21]<4)
    write_byte(ebda_seg,&EbdaData->cdemu.emulated_drive,0x00);
  else
    write_byte(ebda_seg,&EbdaData->cdemu.emulated_drive,0x80);

  write_byte(ebda_seg,&EbdaData->cdemu.controller_index,device/2);
  write_byte(ebda_seg,&EbdaData->cdemu.device_spec,device%2);

  boot_segment=buffer[0x23]*0x100+buffer[0x22];
  if(boot_segment==0x0000)boot_segment=0x07C0;

  write_word(ebda_seg,&EbdaData->cdemu.load_segment,boot_segment);
  write_word(ebda_seg,&EbdaData->cdemu.buffer_segment,0x0000);
  
  nbsectors=buffer[0x27]*0x100+buffer[0x26];
  write_word(ebda_seg,&EbdaData->cdemu.sector_count,nbsectors);

  lba=buffer[0x2B]*0x1000000+buffer[0x2A]*0x10000+buffer[0x29]*0x100+buffer[0x28];
  write_dword(ebda_seg,&EbdaData->cdemu.ilba,lba);

  // And we read the image in memory
  memsetb(get_SS(),atacmd,0,12);
  atacmd[0]=0x28;                      // READ command
  atacmd[7]=((1+(nbsectors-1)/4) & 0xff00) >> 8;      // Sectors
  atacmd[8]=((1+(nbsectors-1)/4) & 0x00ff);           // Sectors
  atacmd[2]=(lba & 0xff000000) >> 24;  // LBA
  atacmd[3]=(lba & 0x00ff0000) >> 16;
  atacmd[4]=(lba & 0x0000ff00) >> 8;
  atacmd[5]=(lba & 0x000000ff);
  if((error = ata_cmd_packet(device, 12, get_SS(), atacmd, 0, nbsectors*512L, ATA_DATA_IN, boot_segment,0)) != 0)
    return 12;

  // Remember the media type
  switch(read_byte(ebda_seg,&EbdaData->cdemu.media)) {
    case 0x01:  // 1.2M floppy
      write_word(ebda_seg,&EbdaData->cdemu.vdevice.spt,15);
      write_word(ebda_seg,&EbdaData->cdemu.vdevice.cylinders,80);
      write_word(ebda_seg,&EbdaData->cdemu.vdevice.heads,2);
      break;
    case 0x02:  // 1.44M floppy
      write_word(ebda_seg,&EbdaData->cdemu.vdevice.spt,18);
      write_word(ebda_seg,&EbdaData->cdemu.vdevice.cylinders,80);
      write_word(ebda_seg,&EbdaData->cdemu.vdevice.heads,2);
      break;
    case 0x03:  // 2.88M floppy
      write_word(ebda_seg,&EbdaData->cdemu.vdevice.spt,36);
      write_word(ebda_seg,&EbdaData->cdemu.vdevice.cylinders,80);
      write_word(ebda_seg,&EbdaData->cdemu.vdevice.heads,2);
      break;
    case 0x04:  // Harddrive
      write_word(ebda_seg,&EbdaData->cdemu.vdevice.spt,read_byte(boot_segment,446+6)&0x3f);
      write_word(ebda_seg,&EbdaData->cdemu.vdevice.cylinders,
	      (read_byte(boot_segment,446+6)<<2) + read_byte(boot_segment,446+7) + 1);
      write_word(ebda_seg,&EbdaData->cdemu.vdevice.heads,read_byte(boot_segment,446+5) + 1);
      break;
   }

  if(read_byte(ebda_seg,&EbdaData->cdemu.media)!=0) {
    // Increase bios installed hardware number of devices
    if(read_byte(ebda_seg,&EbdaData->cdemu.emulated_drive)==0x00)
      write_byte(0x40,0x10,read_byte(0x40,0x10)|0x41);
    else
      write_byte(ebda_seg, &EbdaData->ata.hdcount, read_byte(ebda_seg, &EbdaData->ata.hdcount) + 1);
   }

  
  // everything is ok, so from now on, the emulation is active
  if(read_byte(ebda_seg,&EbdaData->cdemu.media)!=0)
    write_byte(ebda_seg,&EbdaData->cdemu.active,0x01);

  // return the boot drive + no error
  return (read_byte(ebda_seg,&EbdaData->cdemu.emulated_drive)*0x100)+0;
}

// ---------------------------------------------------------------------------
// End of El-Torito boot functions
// ---------------------------------------------------------------------------
#endif // BX_ELTORITO_BOOT

  void
int14_function(regs, ds, iret_addr)
  pusha_regs_t regs; // regs pushed from PUSHA instruction
  Bit16u ds; // previous DS:, DS set to 0x0000 by asm wrapper
  iret_addr_t  iret_addr; // CS,IP,Flags pushed from original INT call
{
  Bit16u addr,timer,val16;
  Bit8u timeout;

  ASM_START
  sti
  ASM_END

  addr = read_word(0x0040, (regs.u.r16.dx << 1));
  timeout = read_byte(0x0040, 0x007C + regs.u.r16.dx);
  if ((regs.u.r16.dx < 4) && (addr > 0)) {
    switch (regs.u.r8.ah) {
      case 0:
        outb(addr+3, inb(addr+3) | 0x80);
        if (regs.u.r8.al & 0xE0 == 0) {
          outb(addr, 0x17);
          outb(addr+1, 0x04);
        } else {
          val16 = 0x600 >> ((regs.u.r8.al & 0xE0) >> 5);
          outb(addr, val16 & 0xFF);
          outb(addr+1, val16 >> 8);
        }
        outb(addr+3, regs.u.r8.al & 0x1F);
        regs.u.r8.ah = inb(addr+5);
        regs.u.r8.al = inb(addr+6);
        ClearCF(iret_addr.flags);
        break;
      case 1:
        timer = read_word(0x0040, 0x006C);
        while (((inb(addr+5) & 0x60) != 0x60) && (timeout)) {
          val16 = read_word(0x0040, 0x006C);
          if (val16 != timer) {
            timer = val16;
            timeout--;
            }
          }
        if (timeout) outb(addr, regs.u.r8.al);
        regs.u.r8.ah = inb(addr+5);
        if (!timeout) regs.u.r8.ah |= 0x80;
        ClearCF(iret_addr.flags);
        break;
      case 2:
        timer = read_word(0x0040, 0x006C);
        while (((inb(addr+5) & 0x01) == 0) && (timeout)) {
          val16 = read_word(0x0040, 0x006C);
          if (val16 != timer) {
            timer = val16;
            timeout--;
            }
          }
        if (timeout) {
          regs.u.r8.ah = 0;
          regs.u.r8.al = inb(addr);
        } else {
          regs.u.r8.ah = inb(addr+5);
          }
        ClearCF(iret_addr.flags);
        break;
      case 3:
        regs.u.r8.ah = inb(addr+5);
        regs.u.r8.al = inb(addr+6);
        ClearCF(iret_addr.flags);
        break;
      default:
        SetCF(iret_addr.flags); // Unsupported
      }
  } else {
    SetCF(iret_addr.flags); // Unsupported
    }
}

  void
int15_function(regs, ES, DS, FLAGS)
  pusha_regs_t regs; // REGS pushed via pusha
  Bit16u ES, DS, FLAGS;
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  bx_bool prev_a20_enable;
  Bit16u  base15_00;
  Bit8u   base23_16;
  Bit16u  ss;
  Bit16u  CX,DX;

  Bit16u bRegister;
  Bit8u irqDisable;

BX_DEBUG_INT15("int15 AX=%04x\n",regs.u.r16.ax);

  switch (regs.u.r8.ah) {
    case 0x24: /* A20 Control */
      switch (regs.u.r8.al) {
        case 0x00:
          set_enable_a20(0);
          CLEAR_CF();
          regs.u.r8.ah = 0;
          break;
        case 0x01:
          set_enable_a20(1);
          CLEAR_CF();
          regs.u.r8.ah = 0;
          break;
        case 0x02:
          regs.u.r8.al = (inb(0x92) >> 1) & 0x01;
          CLEAR_CF();
          regs.u.r8.ah = 0;
          break;
        case 0x03:
          CLEAR_CF();
          regs.u.r8.ah = 0;
          regs.u.r16.bx = 3;
          break;
        default:
          BX_INFO("int15: Func 24h, subfunc %02xh, A20 gate control not supported\n", (unsigned) regs.u.r8.al);
          SET_CF();
          regs.u.r8.ah = UNSUPPORTED_FUNCTION;
      }
      break;

    case 0x41:
      SET_CF();
      regs.u.r8.ah = UNSUPPORTED_FUNCTION;
      break;

    case 0x4f:
      /* keyboard intercept */
#if BX_CPU < 2
      regs.u.r8.ah = UNSUPPORTED_FUNCTION;
#else
      // nop
#endif
      SET_CF();
      break;

    case 0x52:    // removable media eject
      CLEAR_CF();
      regs.u.r8.ah = 0;  // "ok ejection may proceed"
      break;

    case 0x83: {
      if( regs.u.r8.al == 0 ) {
        // Set Interval requested.
        if( ( read_byte( 0x40, 0xA0 ) & 1 ) == 0 ) {
          // Interval not already set.
          write_byte( 0x40, 0xA0, 1 );  // Set status byte.
          write_word( 0x40, 0x98, ES ); // Byte location, segment
          write_word( 0x40, 0x9A, regs.u.r16.bx ); // Byte location, offset
          write_word( 0x40, 0x9C, regs.u.r16.dx ); // Low word, delay
          write_word( 0x40, 0x9E, regs.u.r16.cx ); // High word, delay.
          CLEAR_CF( );
          irqDisable = inb( 0xA1 );
          outb( 0xA1, irqDisable & 0xFE );
          bRegister = inb_cmos( 0xB );  // Unmask IRQ8 so INT70 will get through.
          outb_cmos( 0xB, bRegister | 0x40 ); // Turn on the Periodic Interrupt timer
        } else {
          // Interval already set.
          BX_DEBUG_INT15("int15: Func 83h, failed, already waiting.\n" );
          SET_CF();
          regs.u.r8.ah = UNSUPPORTED_FUNCTION;
        }
      } else if( regs.u.r8.al == 1 ) {
        // Clear Interval requested
        write_byte( 0x40, 0xA0, 0 );  // Clear status byte
        CLEAR_CF( );
        bRegister = inb_cmos( 0xB );
        outb_cmos( 0xB, bRegister & ~0x40 );  // Turn off the Periodic Interrupt timer
      } else {
        BX_DEBUG_INT15("int15: Func 83h, failed.\n" );
        SET_CF();
        regs.u.r8.ah = UNSUPPORTED_FUNCTION;
        regs.u.r8.al--;
      }

      break;
    }

    case 0x87:
#if BX_CPU < 3
#  error "Int15 function 87h not supported on < 80386"
#endif
      // +++ should probably have descriptor checks
      // +++ should have exception handlers

 // turn off interrupts
ASM_START
  cli
ASM_END

      prev_a20_enable = set_enable_a20(1); // enable A20 line

      // 128K max of transfer on 386+ ???
      // source == destination ???

      // ES:SI points to descriptor table
      // offset   use     initially  comments
      // ==============================================
      // 00..07   Unused  zeros      Null descriptor
      // 08..0f   GDT     zeros      filled in by BIOS
      // 10..17   source  ssssssss   source of data
      // 18..1f   dest    dddddddd   destination of data
      // 20..27   CS      zeros      filled in by BIOS
      // 28..2f   SS      zeros      filled in by BIOS

      //es:si
      //eeee0
      //0ssss
      //-----

// check for access rights of source & dest here

      // Initialize GDT descriptor
      base15_00 = (ES << 4) + regs.u.r16.si;
      base23_16 = ES >> 12;
      if (base15_00 < (ES<<4))
        base23_16++;
      write_word(ES, regs.u.r16.si+0x08+0, 47);       // limit 15:00 = 6 * 8bytes/descriptor
      write_word(ES, regs.u.r16.si+0x08+2, base15_00);// base 15:00
      write_byte(ES, regs.u.r16.si+0x08+4, base23_16);// base 23:16
      write_byte(ES, regs.u.r16.si+0x08+5, 0x93);     // access
      write_word(ES, regs.u.r16.si+0x08+6, 0x0000);   // base 31:24/reserved/limit 19:16

      // Initialize CS descriptor
      write_word(ES, regs.u.r16.si+0x20+0, 0xffff);// limit 15:00 = normal 64K limit
      write_word(ES, regs.u.r16.si+0x20+2, 0x0000);// base 15:00
      write_byte(ES, regs.u.r16.si+0x20+4, 0x000f);// base 23:16
      write_byte(ES, regs.u.r16.si+0x20+5, 0x9b);  // access
      write_word(ES, regs.u.r16.si+0x20+6, 0x0000);// base 31:24/reserved/limit 19:16

      // Initialize SS descriptor
      ss = get_SS();
      base15_00 = ss << 4;
      base23_16 = ss >> 12;
      write_word(ES, regs.u.r16.si+0x28+0, 0xffff);   // limit 15:00 = normal 64K limit
      write_word(ES, regs.u.r16.si+0x28+2, base15_00);// base 15:00
      write_byte(ES, regs.u.r16.si+0x28+4, base23_16);// base 23:16
      write_byte(ES, regs.u.r16.si+0x28+5, 0x93);     // access
      write_word(ES, regs.u.r16.si+0x28+6, 0x0000);   // base 31:24/reserved/limit 19:16

      CX = regs.u.r16.cx;
ASM_START
      // Compile generates locals offset info relative to SP.
      // Get CX (word count) from stack.
      mov  bx, sp
      SEG SS
        mov  cx, _int15_function.CX [bx]

      // since we need to set SS:SP, save them to the BDA
      // for future restore
      push eax
      xor eax, eax
      mov ds, ax
      mov 0x0469, ss
      mov 0x0467, sp

      SEG ES
        lgdt [si + 0x08]
      SEG CS
        lidt [pmode_IDT_info]
      ;;  perhaps do something with IDT here

      ;; set PE bit in CR0
      mov  eax, cr0
      or   al, #0x01
      mov  cr0, eax
      ;; far jump to flush CPU queue after transition to protected mode
      JMP_AP(0x0020, protected_mode)

protected_mode:
      ;; GDT points to valid descriptor table, now load SS, DS, ES
      mov  ax, #0x28 ;; 101 000 = 5th descriptor in table, TI=GDT, RPL=00
      mov  ss, ax
      mov  ax, #0x10 ;; 010 000 = 2nd descriptor in table, TI=GDT, RPL=00
      mov  ds, ax
      mov  ax, #0x18 ;; 011 000 = 3rd descriptor in table, TI=GDT, RPL=00
      mov  es, ax
      xor  si, si
      xor  di, di
      cld
      rep
        movsw  ;; move CX words from DS:SI to ES:DI

      ;; make sure DS and ES limits are 64KB
      mov ax, #0x28
      mov ds, ax
      mov es, ax

      ;; reset PG bit in CR0 ???
      mov  eax, cr0
      and  al, #0xFE
      mov  cr0, eax

      ;; far jump to flush CPU queue after transition to real mode
      JMP_AP(0xf000, real_mode)

real_mode:
      ;; restore IDT to normal real-mode defaults
      SEG CS
        lidt [rmode_IDT_info]

      // restore SS:SP from the BDA
      xor ax, ax
      mov ds, ax
      mov ss, 0x0469
      mov sp, 0x0467
      pop eax
ASM_END

      set_enable_a20(prev_a20_enable);

 // turn back on interrupts
ASM_START
  sti
ASM_END

      regs.u.r8.ah = 0;
      CLEAR_CF();
      break;


    case 0x88:
      // Get the amount of extended memory (above 1M)
#if BX_CPU < 2
      regs.u.r8.ah = UNSUPPORTED_FUNCTION;
      SET_CF();
#else
      regs.u.r8.al = inb_cmos(0x30);
      regs.u.r8.ah = inb_cmos(0x31);

      // limit to 15M
      if(regs.u.r16.ax > 0x3c00)
        regs.u.r16.ax = 0x3c00;

      CLEAR_CF();
#endif
      break;

    case 0x90:
      /* Device busy interrupt.  Called by Int 16h when no key available */
      break;

    case 0x91:
      /* Interrupt complete.  Called by Int 16h when key becomes available */
      break;

    case 0xbf:
      BX_INFO("*** int 15h function AH=bf not yet supported!\n");
      SET_CF();
      regs.u.r8.ah = UNSUPPORTED_FUNCTION;
      break;

    case 0xC0:
#if 0
      SET_CF();
      regs.u.r8.ah = UNSUPPORTED_FUNCTION;
      break;
#endif
      CLEAR_CF();
      regs.u.r8.ah = 0;
      regs.u.r16.bx =  BIOS_CONFIG_TABLE;
      ES = 0xF000;
      break;

    case 0xc1:
      ES = ebda_seg;
      CLEAR_CF();
      break;

    case 0xd8:
      bios_printf(BIOS_PRINTF_DEBUG, "EISA BIOS not present\n");
      SET_CF();
      regs.u.r8.ah = UNSUPPORTED_FUNCTION;
      break;

    default:
      BX_INFO("*** int 15h function AX=%04x, BX=%04x not yet supported!\n",
        (unsigned) regs.u.r16.ax, (unsigned) regs.u.r16.bx);
      SET_CF();
      regs.u.r8.ah = UNSUPPORTED_FUNCTION;
      break;
    }
}

#if BX_USE_PS2_MOUSE
  void
int15_function_mouse(regs, ES, DS, FLAGS)
  pusha_regs_t regs; // REGS pushed via pusha
  Bit16u ES, DS, FLAGS;
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit8u  mouse_flags_1, mouse_flags_2;
  Bit16u mouse_driver_seg;
  Bit16u mouse_driver_offset;
  Bit8u  comm_byte, prev_command_byte;
  Bit8u  ret, mouse_data1, mouse_data2, mouse_data3;

BX_DEBUG_INT15("int15 AX=%04x\n",regs.u.r16.ax);

  switch (regs.u.r8.ah) {
    case 0xC2:
      // Return Codes status in AH
      // =========================
      // 00: success
      // 01: invalid subfunction (AL > 7)
      // 02: invalid input value (out of allowable range)
      // 03: interface error
      // 04: resend command received from mouse controller,
      //     device driver should attempt command again
      // 05: cannot enable mouse, since no far call has been installed
      // 80/86: mouse service not implemented

      switch (regs.u.r8.al) {
        case 0: // Disable/Enable Mouse
BX_DEBUG_INT15("case 0:\n");
          switch (regs.u.r8.bh) {
            case 0: // Disable Mouse
BX_DEBUG_INT15("case 0: disable mouse\n");
              inhibit_mouse_int_and_events(); // disable IRQ12 and packets
              ret = send_to_mouse_ctrl(0xF5); // disable mouse command
              if (ret == 0) {
                ret = get_mouse_data(&mouse_data1);
                if ( (ret == 0) || (mouse_data1 == 0xFA) ) {
                  CLEAR_CF();
                  regs.u.r8.ah = 0;
                  return;
                  }
                }

              // error
              SET_CF();
              regs.u.r8.ah = ret;
              return;
              break;

            case 1: // Enable Mouse
BX_DEBUG_INT15("case 1: enable mouse\n");
              mouse_flags_2 = read_byte(ebda_seg, 0x0027);
              if ( (mouse_flags_2 & 0x80) == 0 ) {
                BX_DEBUG_INT15("INT 15h C2 Enable Mouse, no far call handler\n");
                SET_CF();  // error
                regs.u.r8.ah = 5; // no far call installed
                return;
                }
              inhibit_mouse_int_and_events(); // disable IRQ12 and packets
              ret = send_to_mouse_ctrl(0xF4); // enable mouse command
              if (ret == 0) {
                ret = get_mouse_data(&mouse_data1);
                if ( (ret == 0) && (mouse_data1 == 0xFA) ) {
                  enable_mouse_int_and_events(); // turn IRQ12 and packet generation on
                  CLEAR_CF();
                  regs.u.r8.ah = 0;
                  return;
                  }
                }
              SET_CF();
              regs.u.r8.ah = ret;
              return;

            default: // invalid subfunction
              BX_DEBUG_INT15("INT 15h C2 AL=0, BH=%02x\n", (unsigned) regs.u.r8.bh);
              SET_CF();  // error
              regs.u.r8.ah = 1; // invalid subfunction
              return;
            }
          break;

        case 1: // Reset Mouse
        case 5: // Initialize Mouse
BX_DEBUG_INT15("case 1 or 5:\n");
          if (regs.u.r8.al == 5) {
            if (regs.u.r8.bh != 3) {
              SET_CF();
              regs.u.r8.ah = 0x02; // invalid input
              return;
            }
            mouse_flags_2 = read_byte(ebda_seg, 0x0027);
            mouse_flags_2 = (mouse_flags_2 & 0x00) | regs.u.r8.bh;
            mouse_flags_1 = 0x00;
            write_byte(ebda_seg, 0x0026, mouse_flags_1);
            write_byte(ebda_seg, 0x0027, mouse_flags_2);
          }

          inhibit_mouse_int_and_events(); // disable IRQ12 and packets
          ret = send_to_mouse_ctrl(0xFF); // reset mouse command
          if (ret == 0) {
            ret = get_mouse_data(&mouse_data3);
            // if no mouse attached, it will return RESEND
            if (mouse_data3 == 0xfe) {
              SET_CF();
              return;
            }
            if (mouse_data3 != 0xfa)
              BX_PANIC("Mouse reset returned %02x (should be ack)\n", (unsigned)mouse_data3);
            if ( ret == 0 ) {
              ret = get_mouse_data(&mouse_data1);
              if ( ret == 0 ) {
                ret = get_mouse_data(&mouse_data2);
                if ( ret == 0 ) {
                  // turn IRQ12 and packet generation on
                  enable_mouse_int_and_events();
                  CLEAR_CF();
                  regs.u.r8.ah = 0;
                  regs.u.r8.bl = mouse_data1;
                  regs.u.r8.bh = mouse_data2;
                  return;
                  }
                }
              }
            }

          // error
          SET_CF();
          regs.u.r8.ah = ret;
          return;

        case 2: // Set Sample Rate
BX_DEBUG_INT15("case 2:\n");
          switch (regs.u.r8.bh) {
            case 0: mouse_data1 = 10; break; //  10 reports/sec
            case 1: mouse_data1 = 20; break; //  20 reports/sec
            case 2: mouse_data1 = 40; break; //  40 reports/sec
            case 3: mouse_data1 = 60; break; //  60 reports/sec
            case 4: mouse_data1 = 80; break; //  80 reports/sec
            case 5: mouse_data1 = 100; break; // 100 reports/sec (default)
            case 6: mouse_data1 = 200; break; // 200 reports/sec
            default: mouse_data1 = 0;
          }
          if (mouse_data1 > 0) {
            ret = send_to_mouse_ctrl(0xF3); // set sample rate command
            if (ret == 0) {
              ret = get_mouse_data(&mouse_data2);
              ret = send_to_mouse_ctrl(mouse_data1);
              ret = get_mouse_data(&mouse_data2);
              CLEAR_CF();
              regs.u.r8.ah = 0;
            } else {
              // error
              SET_CF();
              regs.u.r8.ah = UNSUPPORTED_FUNCTION;
            }
          } else {
            // error
            SET_CF();
            regs.u.r8.ah = UNSUPPORTED_FUNCTION;
          }
          break;

        case 3: // Set Resolution
BX_DEBUG_INT15("case 3:\n");
          // BX:
          //      0 =  25 dpi, 1 count  per millimeter
          //      1 =  50 dpi, 2 counts per millimeter
          //      2 = 100 dpi, 4 counts per millimeter
          //      3 = 200 dpi, 8 counts per millimeter
          CLEAR_CF();
          regs.u.r8.ah = 0;
          break;

        case 4: // Get Device ID
BX_DEBUG_INT15("case 4:\n");
          inhibit_mouse_int_and_events(); // disable IRQ12 and packets
          ret = send_to_mouse_ctrl(0xF2); // get mouse ID command
          if (ret == 0) {
            ret = get_mouse_data(&mouse_data1);
            ret = get_mouse_data(&mouse_data2);
            CLEAR_CF();
            regs.u.r8.ah = 0;
            regs.u.r8.bh = mouse_data2;
          } else {
            // error
            SET_CF();
            regs.u.r8.ah = UNSUPPORTED_FUNCTION;
          }
          break;

        case 6: // Return Status & Set Scaling Factor...
BX_DEBUG_INT15("case 6:\n");
          switch (regs.u.r8.bh) {
            case 0: // Return Status
              comm_byte = inhibit_mouse_int_and_events(); // disable IRQ12 and packets
              ret = send_to_mouse_ctrl(0xE9); // get mouse info command
              if (ret == 0) {
                ret = get_mouse_data(&mouse_data1);
                if (mouse_data1 != 0xfa)
                  BX_PANIC("Mouse status returned %02x (should be ack)\n", (unsigned)mouse_data1);
                if (ret == 0) {
                  ret = get_mouse_data(&mouse_data1);
                  if ( ret == 0 ) {
                    ret = get_mouse_data(&mouse_data2);
                    if ( ret == 0 ) {
                      ret = get_mouse_data(&mouse_data3);
                      if ( ret == 0 ) {
                        CLEAR_CF();
                        regs.u.r8.ah = 0;
                        regs.u.r8.bl = mouse_data1;
                        regs.u.r8.cl = mouse_data2;
                        regs.u.r8.dl = mouse_data3;
                        set_kbd_command_byte(comm_byte); // restore IRQ12 and serial enable
                        return;
                        }
                      }
                    }
                  }
                }

              // error
              SET_CF();
              regs.u.r8.ah = ret;
              set_kbd_command_byte(comm_byte); // restore IRQ12 and serial enable
              return;

            case 1: // Set Scaling Factor to 1:1
            case 2: // Set Scaling Factor to 2:1
              comm_byte = inhibit_mouse_int_and_events(); // disable IRQ12 and packets
              if (regs.u.r8.bh == 1) {
                ret = send_to_mouse_ctrl(0xE6);
              } else {
                ret = send_to_mouse_ctrl(0xE7);
              }
              if (ret == 0) {
                get_mouse_data(&mouse_data1);
                ret = (mouse_data1 != 0xFA);
              }
              if (ret == 0) {
                CLEAR_CF();
                regs.u.r8.ah = 0;
              } else {
                // error
                SET_CF();
                regs.u.r8.ah = UNSUPPORTED_FUNCTION;
              }
              set_kbd_command_byte(comm_byte); // restore IRQ12 and serial enable
              break;

            default:
              BX_PANIC("INT 15h C2 AL=6, BH=%02x\n", (unsigned) regs.u.r8.bh);
            }
          break;

        case 7: // Set Mouse Handler Address
BX_DEBUG_INT15("case 7:\n");
          mouse_driver_seg = ES;
          mouse_driver_offset = regs.u.r16.bx;
          write_word(ebda_seg, 0x0022, mouse_driver_offset);
          write_word(ebda_seg, 0x0024, mouse_driver_seg);
          mouse_flags_2 = read_byte(ebda_seg, 0x0027);
          if (mouse_driver_offset == 0 && mouse_driver_seg == 0) {
            /* remove handler */
            if ( (mouse_flags_2 & 0x80) != 0 ) {
              mouse_flags_2 &= ~0x80;
              inhibit_mouse_int_and_events(); // disable IRQ12 and packets
              }
            }
          else {
            /* install handler */
            mouse_flags_2 |= 0x80;
            }
          write_byte(ebda_seg, 0x0027, mouse_flags_2);
          CLEAR_CF();
          regs.u.r8.ah = 0;
          break;

        default:
BX_DEBUG_INT15("case default:\n");
          regs.u.r8.ah = 1; // invalid function
          SET_CF();
        }
      break;

    default:
      BX_INFO("*** int 15h function AX=%04x, BX=%04x not yet supported!\n",
        (unsigned) regs.u.r16.ax, (unsigned) regs.u.r16.bx);
      SET_CF();
      regs.u.r8.ah = UNSUPPORTED_FUNCTION;
      break;
    }
}
#endif

  void
int15_function32(regs, ES, DS, FLAGS)
  pushad_regs_t regs; // REGS pushed via pushad
  Bit16u ES, DS, FLAGS;
{
  Bit32u  extended_memory_size=0; // 64bits long
  Bit16u  CX,DX;

BX_DEBUG_INT15("int15 AX=%04x\n",regs.u.r16.ax);

  switch (regs.u.r8.ah) {
    case 0x86:
      // Wait for CX:DX microseconds. currently using the 
      // refresh request port 0x61 bit4, toggling every 15usec 

      CX = regs.u.r16.cx;
      DX = regs.u.r16.dx;

ASM_START
      ;; Get the count in eax
      mov  ax, .int15_function32.CX [bp]
      shl  eax, #16
      mov  ax, .int15_function32.DX [bp]

      ;; convert to numbers of 15usec ticks
      mov ebx, #15
      xor edx, edx
      div eax, ebx
      mov ecx, eax

      ;; wait for ecx number of refresh requests
      in al, #0x61
      and al,#0x10
      mov ah, al

      or ecx, ecx
      je int1586_tick_end
int1586_tick:
      in al, #0x61
      and al,#0x10
      cmp al, ah
      je  int1586_tick
      mov ah, al
      dec ecx
      jnz int1586_tick
int1586_tick_end:
ASM_END

      break;

    case 0xe8:
        switch(regs.u.r8.al)
        {
        case 0x20: {
            Bit16u e820_table_size = read_word(0xe000, 0x8) * 0x14;

            if (regs.u.r32.edx != 0x534D4150) /* SMAP */
                goto int15_unimplemented;

            if ((regs.u.r16.bx / 0x14) * 0x14 == regs.u.r16.bx) {
                if (regs.u.r16.bx + 0x14 <= e820_table_size)
                    memcpyb(ES, regs.u.r16.di,
                            0xe000, 0x10 + regs.u.r16.bx, 0x14);
                regs.u.r32.ebx += 0x14;
                if ((regs.u.r32.ebx + 0x14 - 1) > e820_table_size)
                    regs.u.r32.ebx = 0;
            } else if (regs.u.r16.bx == 1) {
                Bit32u base, type;
                Bit16u off;
                for (off = 0; off < e820_table_size; off += 0x14) {
                    base = read_dword(0xe000, 0x10 + off);
                    type = read_dword(0xe000, 0x20 + off);
                    if ((base >= 0x100000) && (type == 1))
                        break;
                }
                if (off == e820_table_size) {
                    SET_CF();
                    break;
                }
                memcpyb(ES, regs.u.r16.di, 0xe000, 0x10 + off, 0x14);
                regs.u.r32.ebx = 0;
            } else { /* AX=E820, DX=534D4150, BX unrecognized */
                goto int15_unimplemented;
            }

            regs.u.r32.eax = 0x534D4150;
            regs.u.r32.ecx = 0x14;
            CLEAR_CF();
            break;
        }

        case 0x01: {
            Bit16u off, e820_table_size = read_word(0xe000, 0x8) * 0x14;
            Bit32u base, type, size;

            // do we have any reason to fail here ?
            CLEAR_CF();

            // Get the amount of extended memory (above 1M)
            regs.u.r8.cl = inb_cmos(0x30);
            regs.u.r8.ch = inb_cmos(0x31);
          
            // limit to 15M
            if (regs.u.r16.cx > (15*1024))
                regs.u.r16.cx = 15*1024;

            // Find first RAM E820 entry >= 1MB.
            for (off = 0; off < e820_table_size; off += 0x14) {
                base = read_dword(0xe000, 0x10 + off);
                type = read_dword(0xe000, 0x20 + off);
                if ((base >= 0x100000) && (type == 1))
                    break;
            }

            // If there is RAM above 16MB, return amount in 64kB chunks.
            regs.u.r16.dx = 0;
            if (off != e820_table_size) {
                size = base + read_dword(0xe000, 0x18 + off);
                if (size > 0x1000000) {
                    size -= 0x1000000;
                    regs.u.r16.dx = (Bit16u)(size >> 16);
                }
            }

            // Set configured memory equal to extended memory
            regs.u.r16.ax = regs.u.r16.cx;
            regs.u.r16.bx = regs.u.r16.dx;
            break;
        }
	default:  /* AH=0xE8?? but not implemented */
            goto int15_unimplemented;
        }
        break;
    int15_unimplemented:
       // fall into the default
    default:
      BX_INFO("*** int 15h function AX=%04x, BX=%04x not yet supported!\n",
        (unsigned) regs.u.r16.ax, (unsigned) regs.u.r16.bx);
      SET_CF();
      regs.u.r8.ah = UNSUPPORTED_FUNCTION;
      break;
    }
}

  void
int16_function(DI, SI, BP, SP, BX, DX, CX, AX, FLAGS)
  Bit16u DI, SI, BP, SP, BX, DX, CX, AX, FLAGS;
{
  Bit8u scan_code, ascii_code, shift_flags, count;
  Bit16u kbd_code, max;

  BX_DEBUG_INT16("int16: AX=%04x BX=%04x CX=%04x DX=%04x \n", AX, BX, CX, DX);

  switch (GET_AH()) {
    case 0x00: /* read keyboard input */

      if ( !dequeue_key(&scan_code, &ascii_code, 1) ) {
        BX_PANIC("KBD: int16h: out of keyboard input\n");
        }
      if (scan_code !=0 && ascii_code == 0xF0) ascii_code = 0;
      else if (ascii_code == 0xE0) ascii_code = 0;
      AX = (scan_code << 8) | ascii_code;
      break;

    case 0x01: /* check keyboard status */
      if ( !dequeue_key(&scan_code, &ascii_code, 0) ) {
        SET_ZF();
        return;
        }
      if (scan_code !=0 && ascii_code == 0xF0) ascii_code = 0;
      else if (ascii_code == 0xE0) ascii_code = 0;
      AX = (scan_code << 8) | ascii_code;
      CLEAR_ZF();
      break;

    case 0x02: /* get shift flag status */
      shift_flags = read_byte(0x0040, 0x17);
      SET_AL(shift_flags);
      break;

    case 0x05: /* store key-stroke into buffer */
      if ( !enqueue_key(GET_CH(), GET_CL()) ) {
        SET_AL(1);
        }
      else {
        SET_AL(0);
        }
      break;

    case 0x09: /* GET KEYBOARD FUNCTIONALITY */
      // bit Bochs Description     
      //  7    0   reserved
      //  6    0   INT 16/AH=20h-22h supported (122-key keyboard support)
      //  5    1   INT 16/AH=10h-12h supported (enhanced keyboard support)
      //  4    1   INT 16/AH=0Ah supported
      //  3    0   INT 16/AX=0306h supported
      //  2    0   INT 16/AX=0305h supported
      //  1    0   INT 16/AX=0304h supported
      //  0    0   INT 16/AX=0300h supported
      //
      SET_AL(0x30);
      break;

    case 0x0A: /* GET KEYBOARD ID */
      count = 2;
      kbd_code = 0x0;
      outb(0x60, 0xf2);
      /* Wait for data */
      max=0xffff;
      while ( ((inb(0x64) & 0x01) == 0) && (--max>0) ) outb(0x80, 0x00);
      if (max>0x0) {
        if ((inb(0x60) == 0xfa)) {
          do {
            max=0xffff;
            while ( ((inb(0x64) & 0x01) == 0) && (--max>0) ) outb(0x80, 0x00);
            if (max>0x0) {
              kbd_code >>= 8;
              kbd_code |= (inb(0x60) << 8);
            }
          } while (--count>0);
	}
      }
      BX=kbd_code;
      break;

    case 0x10: /* read MF-II keyboard input */

      if ( !dequeue_key(&scan_code, &ascii_code, 1) ) {
        BX_PANIC("KBD: int16h: out of keyboard input\n");
        }
      if (scan_code !=0 && ascii_code == 0xF0) ascii_code = 0;
      AX = (scan_code << 8) | ascii_code;
      break;

    case 0x11: /* check MF-II keyboard status */
      if ( !dequeue_key(&scan_code, &ascii_code, 0) ) {
        SET_ZF();
        return;
        }
      if (scan_code !=0 && ascii_code == 0xF0) ascii_code = 0;
      AX = (scan_code << 8) | ascii_code;
      CLEAR_ZF();
      break;

    case 0x12: /* get extended keyboard status */
      shift_flags = read_byte(0x0040, 0x17);
      SET_AL(shift_flags);
      shift_flags = read_byte(0x0040, 0x18);
      SET_AH(shift_flags);
      BX_DEBUG_INT16("int16: func 12 sending %04x\n",AX);
      break;

    case 0x92: /* keyboard capability check called by DOS 5.0+ keyb */
      SET_AH(0x80); // function int16 ah=0x10-0x12 supported
      break;

    case 0xA2: /* 122 keys capability check called by DOS 5.0+ keyb */
      // don't change AH : function int16 ah=0x20-0x22 NOT supported
      break;

    case 0x6F:
      if (GET_AL() == 0x08)
	SET_AH(0x02); // unsupported, aka normal keyboard

    default:
      BX_INFO("KBD: unsupported int 16h function %02x\n", GET_AH());
    }
}

  unsigned int
dequeue_key(scan_code, ascii_code, incr)
  Bit8u *scan_code;
  Bit8u *ascii_code;
  unsigned int incr;
{
  Bit16u buffer_start, buffer_end, buffer_head, buffer_tail;
  Bit16u ss;
  Bit8u  acode, scode;

#if BX_CPU < 2
  buffer_start = 0x001E;
  buffer_end   = 0x003E;
#else
  buffer_start = read_word(0x0040, 0x0080);
  buffer_end   = read_word(0x0040, 0x0082);
#endif

  buffer_head = read_word(0x0040, 0x001a);
  buffer_tail = read_word(0x0040, 0x001c);

  if (buffer_head != buffer_tail) {
    ss = get_SS();
    acode = read_byte(0x0040, buffer_head);
    scode = read_byte(0x0040, buffer_head+1);
    write_byte(ss, ascii_code, acode);
    write_byte(ss, scan_code, scode);

    if (incr) {
      buffer_head += 2;
      if (buffer_head >= buffer_end)
        buffer_head = buffer_start;
      write_word(0x0040, 0x001a, buffer_head);
      }
    return(1);
    }
  else {
    return(0);
    }
}

static char panic_msg_keyb_buffer_full[] = "%s: keyboard input buffer full\n";

  Bit8u
inhibit_mouse_int_and_events()
{
  Bit8u command_byte, prev_command_byte;

  // Turn off IRQ generation and aux data line
  if ( inb(0x64) & 0x02 )
    BX_PANIC(panic_msg_keyb_buffer_full,"inhibmouse");
  outb(0x64, 0x20); // get command byte
  while ( (inb(0x64) & 0x01) != 0x01 );
  prev_command_byte = inb(0x60);
  command_byte = prev_command_byte;
  //while ( (inb(0x64) & 0x02) );
  if ( inb(0x64) & 0x02 )
    BX_PANIC(panic_msg_keyb_buffer_full,"inhibmouse");
  command_byte &= 0xfd; // turn off IRQ 12 generation
  command_byte |= 0x20; // disable mouse serial clock line
  outb(0x64, 0x60); // write command byte
  outb(0x60, command_byte);
  return(prev_command_byte);
}

  void
enable_mouse_int_and_events()
{
  Bit8u command_byte;

  // Turn on IRQ generation and aux data line
  if ( inb(0x64) & 0x02 )
    BX_PANIC(panic_msg_keyb_buffer_full,"enabmouse");
  outb(0x64, 0x20); // get command byte
  while ( (inb(0x64) & 0x01) != 0x01 );
  command_byte = inb(0x60);
  //while ( (inb(0x64) & 0x02) );
  if ( inb(0x64) & 0x02 )
    BX_PANIC(panic_msg_keyb_buffer_full,"enabmouse");
  command_byte |= 0x02; // turn on IRQ 12 generation
  command_byte &= 0xdf; // enable mouse serial clock line
  outb(0x64, 0x60); // write command byte
  outb(0x60, command_byte);
}

  Bit8u
send_to_mouse_ctrl(sendbyte)
  Bit8u sendbyte;
{
  Bit8u response;

  // wait for chance to write to ctrl
  if ( inb(0x64) & 0x02 )
    BX_PANIC(panic_msg_keyb_buffer_full,"sendmouse");
  outb(0x64, 0xD4);
  outb(0x60, sendbyte);
  return(0);
}


  Bit8u
get_mouse_data(data)
  Bit8u *data;
{
  Bit8u response;
  Bit16u ss;

  while ( (inb(0x64) & 0x21) != 0x21 ) {
    }

  response = inb(0x60);

  ss = get_SS();
  write_byte(ss, data, response);
  return(0);
}

  void
set_kbd_command_byte(command_byte)
  Bit8u command_byte;
{
  if ( inb(0x64) & 0x02 )
    BX_PANIC(panic_msg_keyb_buffer_full,"setkbdcomm");
  outb(0x64, 0xD4);

  outb(0x64, 0x60); // write command byte
  outb(0x60, command_byte);
}

  void
int09_function(DI, SI, BP, SP, BX, DX, CX, AX)
  Bit16u DI, SI, BP, SP, BX, DX, CX, AX;
{
  Bit8u scancode, asciicode, shift_flags;
  Bit8u mf2_flags, mf2_state, led_flags;

  //
  // DS has been set to F000 before call
  //


  scancode = GET_AL();

  if (scancode == 0) {
    BX_INFO("KBD: int09 handler: AL=0\n");
    return;
    }


  shift_flags = read_byte(0x0040, 0x17);
  mf2_flags = read_byte(0x0040, 0x18);
  mf2_state = read_byte(0x0040, 0x96);
  led_flags = read_byte(0x0040, 0x97);
  asciicode = 0;

  switch (scancode) {
    case 0x3a: /* Caps Lock press */
      shift_flags ^= 0x40;
      write_byte(0x0040, 0x17, shift_flags);
      mf2_flags |= 0x40;
      write_byte(0x0040, 0x18, mf2_flags);
      led_flags ^= 0x04;
      write_byte(0x0040, 0x97, led_flags);
      break;
    case 0xba: /* Caps Lock release */
      mf2_flags &= ~0x40;
      write_byte(0x0040, 0x18, mf2_flags);
      break;

    case 0x2a: /* L Shift press */
      /*shift_flags &= ~0x40;*/
      shift_flags |= 0x02;
      write_byte(0x0040, 0x17, shift_flags);
      led_flags &= ~0x04;
      write_byte(0x0040, 0x97, led_flags);
      break;
    case 0xaa: /* L Shift release */
      shift_flags &= ~0x02;
      write_byte(0x0040, 0x17, shift_flags);
      break;

    case 0x36: /* R Shift press */
      /*shift_flags &= ~0x40;*/
      shift_flags |= 0x01;
      write_byte(0x0040, 0x17, shift_flags);
      led_flags &= ~0x04;
      write_byte(0x0040, 0x97, led_flags);
      break;
    case 0xb6: /* R Shift release */
      shift_flags &= ~0x01;
      write_byte(0x0040, 0x17, shift_flags);
      break;

    case 0x1d: /* Ctrl press */
      shift_flags |= 0x04;
      write_byte(0x0040, 0x17, shift_flags);
      if (mf2_state & 0x01) {
        mf2_flags |= 0x04;
      } else {
        mf2_flags |= 0x01;
        }
      write_byte(0x0040, 0x18, mf2_flags);
      break;
    case 0x9d: /* Ctrl release */
      shift_flags &= ~0x04;
      write_byte(0x0040, 0x17, shift_flags);
      if (mf2_state & 0x01) {
        mf2_flags &= ~0x04;
      } else {
        mf2_flags &= ~0x01;
        }
      write_byte(0x0040, 0x18, mf2_flags);
      break;

    case 0x38: /* Alt press */
      shift_flags |= 0x08;
      write_byte(0x0040, 0x17, shift_flags);
      if (mf2_state & 0x01) {
        mf2_flags |= 0x08;
      } else {
        mf2_flags |= 0x02;
        }
      write_byte(0x0040, 0x18, mf2_flags);
      break;
    case 0xb8: /* Alt release */
      shift_flags &= ~0x08;
      write_byte(0x0040, 0x17, shift_flags);
      if (mf2_state & 0x01) {
        mf2_flags &= ~0x08;
      } else {
        mf2_flags &= ~0x02;
        }
      write_byte(0x0040, 0x18, mf2_flags);
      break;

    case 0x45: /* Num Lock press */
      if ((mf2_state & 0x01) == 0) {
        mf2_flags |= 0x20;
        write_byte(0x0040, 0x18, mf2_flags);
        shift_flags ^= 0x20;
        led_flags ^= 0x02;
        write_byte(0x0040, 0x17, shift_flags);
        write_byte(0x0040, 0x97, led_flags);
        }
      break;
    case 0xc5: /* Num Lock release */
      if ((mf2_state & 0x01) == 0) {
        mf2_flags &= ~0x20;
        write_byte(0x0040, 0x18, mf2_flags);
        }
      break;

    case 0x46: /* Scroll Lock press */
      mf2_flags |= 0x10;
      write_byte(0x0040, 0x18, mf2_flags);
      shift_flags ^= 0x10;
      led_flags ^= 0x01;
      write_byte(0x0040, 0x17, shift_flags);
      write_byte(0x0040, 0x97, led_flags);
      break;

    case 0xc6: /* Scroll Lock release */
      mf2_flags &= ~0x10;
      write_byte(0x0040, 0x18, mf2_flags);
      break;

    default:
      if (scancode & 0x80) return; /* toss key releases ... */
      if (scancode > MAX_SCAN_CODE) {
        BX_INFO("KBD: int09h_handler(): unknown scancode read!\n");
        return;
        }
      if (shift_flags & 0x08) { /* ALT */
        asciicode = scan_to_scanascii[scancode].alt;
        scancode = scan_to_scanascii[scancode].alt >> 8;
        }
      else if (shift_flags & 0x04) { /* CONTROL */
        asciicode = scan_to_scanascii[scancode].control;
        scancode = scan_to_scanascii[scancode].control >> 8;
        }
      else if (shift_flags & 0x03) { /* LSHIFT + RSHIFT */
        /* check if lock state should be ignored 
         * because a SHIFT key are pressed */
         
        if (shift_flags & scan_to_scanascii[scancode].lock_flags) {
          asciicode = scan_to_scanascii[scancode].normal;
          scancode = scan_to_scanascii[scancode].normal >> 8;
          }
        else {
          asciicode = scan_to_scanascii[scancode].shift;
          scancode = scan_to_scanascii[scancode].shift >> 8;
          }
        }
      else {
        /* check if lock is on */
        if (shift_flags & scan_to_scanascii[scancode].lock_flags) {
          asciicode = scan_to_scanascii[scancode].shift;
          scancode = scan_to_scanascii[scancode].shift >> 8;
          }
        else {
          asciicode = scan_to_scanascii[scancode].normal;
          scancode = scan_to_scanascii[scancode].normal >> 8;
          }
        }
      if (scancode==0 && asciicode==0) {
        BX_INFO("KBD: int09h_handler(): scancode & asciicode are zero?\n");
        }
      enqueue_key(scancode, asciicode);
      break;
    }
  mf2_state &= ~0x01;
}

  unsigned int
enqueue_key(scan_code, ascii_code)
  Bit8u scan_code, ascii_code;
{
  Bit16u buffer_start, buffer_end, buffer_head, buffer_tail, temp_tail;

  //BX_INFO("KBD:   enqueue_key() called scan:%02x, ascii:%02x\n",
  //    scan_code, ascii_code);

#if BX_CPU < 2
  buffer_start = 0x001E;
  buffer_end   = 0x003E;
#else
  buffer_start = read_word(0x0040, 0x0080);
  buffer_end   = read_word(0x0040, 0x0082);
#endif

  buffer_head = read_word(0x0040, 0x001A);
  buffer_tail = read_word(0x0040, 0x001C);

  temp_tail = buffer_tail;
  buffer_tail += 2;
  if (buffer_tail >= buffer_end)
    buffer_tail = buffer_start;

  if (buffer_tail == buffer_head) {
    return(0);
    }

   write_byte(0x0040, temp_tail, ascii_code);
   write_byte(0x0040, temp_tail+1, scan_code);
   write_word(0x0040, 0x001C, buffer_tail);
   return(1);
}


  void
int74_function(make_farcall, Z, Y, X, status)
  Bit16u make_farcall, Z, Y, X, status;
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit8u  in_byte, index, package_count;
  Bit8u  mouse_flags_1, mouse_flags_2;

BX_DEBUG_INT74("entering int74_function\n");
  make_farcall = 0;

  in_byte = inb(0x64);
  if ( (in_byte & 0x21) != 0x21 ) {
    return;
    }
  in_byte = inb(0x60);
BX_DEBUG_INT74("int74: read byte %02x\n", in_byte);

  mouse_flags_1 = read_byte(ebda_seg, 0x0026);
  mouse_flags_2 = read_byte(ebda_seg, 0x0027);

  if ( (mouse_flags_2 & 0x80) != 0x80 ) {
      //    BX_PANIC("int74_function:\n");
      return;
    }

  package_count = mouse_flags_2 & 0x07;
  index = mouse_flags_1 & 0x07;
  write_byte(ebda_seg, 0x28 + index, in_byte);

  if ( (index+1) >= package_count ) {
BX_DEBUG_INT74("int74_function: make_farcall=1\n");
    status = read_byte(ebda_seg, 0x0028 + 0);
    X      = read_byte(ebda_seg, 0x0028 + 1);
    Y      = read_byte(ebda_seg, 0x0028 + 2);
    Z      = 0;
    mouse_flags_1 = 0;
    // check if far call handler installed
    if (mouse_flags_2 & 0x80)
      make_farcall = 1;
    }
  else {
    mouse_flags_1++;
    }
  write_byte(ebda_seg, 0x0026, mouse_flags_1);
}

#define SET_DISK_RET_STATUS(status) write_byte(0x0040, 0x0074, status)

#if BX_USE_ATADRV

  void
int13_harddisk(DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS)
  Bit16u DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS;
{
  Bit32u lba;
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit16u cylinder, head, sector;
  Bit16u segment, offset;
  Bit16u npc, nph, npspt, nlc, nlh, nlspt;
  Bit16u size, count;
  Bit8u  device, status;

  BX_DEBUG_INT13_HD("int13_harddisk: AX=%04x BX=%04x CX=%04x DX=%04x ES=%04x\n", AX, BX, CX, DX, ES);

  write_byte(0x0040, 0x008e, 0);  // clear completion flag

  // basic check : device has to be defined
  if ( (GET_ELDL() < 0x80) || (GET_ELDL() >= 0x80 + BX_MAX_ATA_DEVICES) ) {
    BX_INFO("int13_harddisk: function %02x, ELDL out of range %02x\n", GET_AH(), GET_ELDL());
    goto int13_fail;
    }

  // Get the ata channel
  device=read_byte(ebda_seg,&EbdaData->ata.hdidmap[GET_ELDL()-0x80]);

  // basic check : device has to be valid 
  if (device >= BX_MAX_ATA_DEVICES) {
    BX_INFO("int13_harddisk: function %02x, unmapped device for ELDL=%02x\n", GET_AH(), GET_ELDL());
    goto int13_fail;
    }
  
  switch (GET_AH()) {

    case 0x00: /* disk controller reset */
      ata_reset (device);
      goto int13_success;
      break;

    case 0x01: /* read disk status */
      status = read_byte(0x0040, 0x0074);
      SET_AH(status);
      SET_DISK_RET_STATUS(0);
      /* set CF if error status read */
      if (status) goto int13_fail_nostatus;
      else        goto int13_success_noah;
      break;

    case 0x02: // read disk sectors
    case 0x03: // write disk sectors 
    case 0x04: // verify disk sectors

      count       = GET_AL();
      cylinder    = GET_CH();
      cylinder   |= ( ((Bit16u) GET_CL()) << 2) & 0x300;
      sector      = (GET_CL() & 0x3f);
      head        = GET_DH();

      segment = ES;
      offset  = BX;

      if ( (count > 128) || (count == 0) ) {
        BX_INFO("int13_harddisk: function %02x, count out of range!\n",GET_AH());
        goto int13_fail;
        }

      nlc   = read_word(ebda_seg, &EbdaData->ata.devices[device].lchs.cylinders);
      nlh   = read_word(ebda_seg, &EbdaData->ata.devices[device].lchs.heads);
      nlspt = read_word(ebda_seg, &EbdaData->ata.devices[device].lchs.spt);

      // sanity check on cyl heads, sec
      if( (cylinder >= nlc) || (head >= nlh) || (sector > nlspt )) {
        BX_INFO("int13_harddisk: function %02x, parameters out of range %04x/%04x/%04x!\n", GET_AH(), cylinder, head, sector);
        goto int13_fail;
        }
      
      // FIXME verify
      if ( GET_AH() == 0x04 ) goto int13_success;

      nph   = read_word(ebda_seg, &EbdaData->ata.devices[device].pchs.heads);
      npspt = read_word(ebda_seg, &EbdaData->ata.devices[device].pchs.spt);

      // if needed, translate lchs to lba, and execute command
      if ( (nph != nlh) || (npspt != nlspt)) {
        lba = ((((Bit32u)cylinder * (Bit32u)nlh) + (Bit32u)head) * (Bit32u)nlspt) + (Bit32u)sector - 1;
        sector = 0; // this forces the command to be lba
        }

      if ( GET_AH() == 0x02 )
        status=ata_cmd_data_in(device, ATA_CMD_READ_SECTORS, count, cylinder, head, sector, lba, segment, offset);
      else
        status=ata_cmd_data_out(device, ATA_CMD_WRITE_SECTORS, count, cylinder, head, sector, lba, segment, offset);

      // Set nb of sector transferred
      SET_AL(read_word(ebda_seg, &EbdaData->ata.trsfsectors));

      if (status != 0) {
        BX_INFO("int13_harddisk: function %02x, error %02x !\n",GET_AH(),status);
        SET_AH(0x0c);
        goto int13_fail_noah;
        }

      goto int13_success;
      break;

    case 0x05: /* format disk track */
      BX_INFO("format disk track called\n");
      goto int13_success;
      return;
      break;

    case 0x08: /* read disk drive parameters */
      
      // Get logical geometry from table
      nlc   = read_word(ebda_seg, &EbdaData->ata.devices[device].lchs.cylinders);
      nlh   = read_word(ebda_seg, &EbdaData->ata.devices[device].lchs.heads);
      nlspt = read_word(ebda_seg, &EbdaData->ata.devices[device].lchs.spt);
      count = read_byte(ebda_seg, &EbdaData->ata.hdcount);

      nlc = nlc - 2; /* 0 based , last sector not used */
      SET_AL(0);
      SET_CH(nlc & 0xff);
      SET_CL(((nlc >> 2) & 0xc0) | (nlspt & 0x3f));
      SET_DH(nlh - 1);
      SET_DL(count); /* FIXME returns 0, 1, or n hard drives */

      // FIXME should set ES & DI
      
      goto int13_success;
      break;

    case 0x10: /* check drive ready */
      // should look at 40:8E also???
      
      // Read the status from controller
      status = inb(read_word(ebda_seg, &EbdaData->ata.channels[device/2].iobase1) + ATA_CB_STAT);
      if ( (status & ( ATA_CB_STAT_BSY | ATA_CB_STAT_RDY )) == ATA_CB_STAT_RDY ) {
        goto int13_success;
        }
      else {
        SET_AH(0xAA);
        goto int13_fail_noah;
        }
      break;

    case 0x15: /* read disk drive size */

      // Get physical geometry from table
      npc   = read_word(ebda_seg, &EbdaData->ata.devices[device].pchs.cylinders);
      nph   = read_word(ebda_seg, &EbdaData->ata.devices[device].pchs.heads);
      npspt = read_word(ebda_seg, &EbdaData->ata.devices[device].pchs.spt);

      // Compute sector count seen by int13
      lba = (Bit32u)(npc - 1) * (Bit32u)nph * (Bit32u)npspt;
      CX = lba >> 16;
      DX = lba & 0xffff;

      SET_AH(3);  // hard disk accessible
      goto int13_success_noah;
      break;

    case 0x41: // IBM/MS installation check
      BX=0xaa55;     // install check
      SET_AH(0x30);  // EDD 3.0
      CX=0x0007;     // ext disk access and edd, removable supported
      goto int13_success_noah;
      break;

    case 0x42: // IBM/MS extended read
    case 0x43: // IBM/MS extended write
    case 0x44: // IBM/MS verify
    case 0x47: // IBM/MS extended seek

      count=read_word(DS, SI+(Bit16u)&Int13Ext->count);
      segment=read_word(DS, SI+(Bit16u)&Int13Ext->segment);
      offset=read_word(DS, SI+(Bit16u)&Int13Ext->offset);
 
      // Can't use 64 bits lba
      lba=read_dword(DS, SI+(Bit16u)&Int13Ext->lba2);
      if (lba != 0L) {
        BX_PANIC("int13_harddisk: function %02x. Can't use 64bits lba\n",GET_AH());
        goto int13_fail;
        }

      // Get 32 bits lba and check
      lba=read_dword(DS, SI+(Bit16u)&Int13Ext->lba1);
      if (lba >= read_dword(ebda_seg, &EbdaData->ata.devices[device].sectors) ) {
        BX_INFO("int13_harddisk: function %02x. LBA out of range\n",GET_AH());
        goto int13_fail;
        }

      // If verify or seek
      if (( GET_AH() == 0x44 ) || ( GET_AH() == 0x47 ))
        goto int13_success;
      
      // Execute the command
      if ( GET_AH() == 0x42 )
        status=ata_cmd_data_in(device, ATA_CMD_READ_SECTORS, count, 0, 0, 0, lba, segment, offset);
      else
        status=ata_cmd_data_out(device, ATA_CMD_WRITE_SECTORS, count, 0, 0, 0, lba, segment, offset);

      count=read_word(ebda_seg, &EbdaData->ata.trsfsectors);
      write_word(DS, SI+(Bit16u)&Int13Ext->count, count);

      if (status != 0) {
        BX_INFO("int13_harddisk: function %02x, error %02x !\n",GET_AH(),status);
        SET_AH(0x0c);
        goto int13_fail_noah;
        }

      goto int13_success;
      break;

    case 0x45: // IBM/MS lock/unlock drive
    case 0x49: // IBM/MS extended media change
      goto int13_success;    // Always success for HD
      break;
      
    case 0x46: // IBM/MS eject media
      SET_AH(0xb2);          // Volume Not Removable
      goto int13_fail_noah;  // Always fail for HD
      break;

    case 0x48: // IBM/MS get drive parameters
      size=read_word(DS,SI+(Bit16u)&Int13DPT->size);

      // Buffer is too small
      if(size < 0x1a) 
        goto int13_fail;

      // EDD 1.x
      if(size >= 0x1a) {
        Bit16u   blksize;

        npc     = read_word(ebda_seg, &EbdaData->ata.devices[device].pchs.cylinders);
        nph     = read_word(ebda_seg, &EbdaData->ata.devices[device].pchs.heads);
        npspt   = read_word(ebda_seg, &EbdaData->ata.devices[device].pchs.spt);
        lba     = read_dword(ebda_seg, &EbdaData->ata.devices[device].sectors);
        blksize = read_word(ebda_seg, &EbdaData->ata.devices[device].blksize);

        write_word(DS, SI+(Bit16u)&Int13DPT->size, 0x1a);
        write_word(DS, SI+(Bit16u)&Int13DPT->infos, 0x02); // geometry is valid
        write_dword(DS, SI+(Bit16u)&Int13DPT->cylinders, (Bit32u)npc);
        write_dword(DS, SI+(Bit16u)&Int13DPT->heads, (Bit32u)nph);
        write_dword(DS, SI+(Bit16u)&Int13DPT->spt, (Bit32u)npspt);
        write_dword(DS, SI+(Bit16u)&Int13DPT->sector_count1, lba);  // FIXME should be Bit64
        write_dword(DS, SI+(Bit16u)&Int13DPT->sector_count2, 0L);  
        write_word(DS, SI+(Bit16u)&Int13DPT->blksize, blksize);  
        }

      // EDD 2.x
      if(size >= 0x1e) {
        Bit8u  channel, dev, irq, mode, checksum, i, translation;
        Bit16u iobase1, iobase2, options;

        write_word(DS, SI+(Bit16u)&Int13DPT->size, 0x1e);

        write_word(DS, SI+(Bit16u)&Int13DPT->dpte_segment, ebda_seg);  
        write_word(DS, SI+(Bit16u)&Int13DPT->dpte_offset, &EbdaData->ata.dpte);  

        // Fill in dpte
        channel = device / 2;
        iobase1 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase1);
        iobase2 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase2);
        irq = read_byte(ebda_seg, &EbdaData->ata.channels[channel].irq);
        mode = read_byte(ebda_seg, &EbdaData->ata.devices[device].mode);
        translation = read_byte(ebda_seg, &EbdaData->ata.devices[device].translation);

        options  = (translation==ATA_TRANSLATION_NONE?0:1<<3); // chs translation
        options |= (1<<4); // lba translation
        options |= (mode==ATA_MODE_PIO32?1:0<<7);
        options |= (translation==ATA_TRANSLATION_LBA?1:0<<9); 
        options |= (translation==ATA_TRANSLATION_RECHS?3:0<<9); 

        write_word(ebda_seg, &EbdaData->ata.dpte.iobase1, iobase1);
        write_word(ebda_seg, &EbdaData->ata.dpte.iobase2, iobase2);
        write_byte(ebda_seg, &EbdaData->ata.dpte.prefix, (0xe | (device % 2))<<4 );
        write_byte(ebda_seg, &EbdaData->ata.dpte.unused, 0xcb );
        write_byte(ebda_seg, &EbdaData->ata.dpte.irq, irq );
        write_byte(ebda_seg, &EbdaData->ata.dpte.blkcount, 1 );
        write_byte(ebda_seg, &EbdaData->ata.dpte.dma, 0 );
        write_byte(ebda_seg, &EbdaData->ata.dpte.pio, 0 );
        write_word(ebda_seg, &EbdaData->ata.dpte.options, options);
        write_word(ebda_seg, &EbdaData->ata.dpte.reserved, 0);
        write_byte(ebda_seg, &EbdaData->ata.dpte.revision, 0x11);
 
        checksum=0;
        for (i=0; i<15; i++) checksum+=read_byte(ebda_seg, (&EbdaData->ata.dpte) + i);
        checksum = ~checksum;
        write_byte(ebda_seg, &EbdaData->ata.dpte.checksum, checksum);
        }

      // EDD 3.x
      if(size >= 0x42) {
        Bit8u channel, iface, checksum, i;
        Bit16u iobase1;

        channel = device / 2;
        iface = read_byte(ebda_seg, &EbdaData->ata.channels[channel].iface);
        iobase1 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase1);

        write_word(DS, SI+(Bit16u)&Int13DPT->size, 0x42);
        write_word(DS, SI+(Bit16u)&Int13DPT->key, 0xbedd);
        write_byte(DS, SI+(Bit16u)&Int13DPT->dpi_length, 0x24);
        write_byte(DS, SI+(Bit16u)&Int13DPT->reserved1, 0);
        write_word(DS, SI+(Bit16u)&Int13DPT->reserved2, 0);

        if (iface==ATA_IFACE_ISA) {
          write_byte(DS, SI+(Bit16u)&Int13DPT->host_bus[0], 'I');
          write_byte(DS, SI+(Bit16u)&Int13DPT->host_bus[1], 'S');
          write_byte(DS, SI+(Bit16u)&Int13DPT->host_bus[2], 'A');
          write_byte(DS, SI+(Bit16u)&Int13DPT->host_bus[3], 0);
          }
        else { 
          // FIXME PCI
          }
        write_byte(DS, SI+(Bit16u)&Int13DPT->iface_type[0], 'A');
        write_byte(DS, SI+(Bit16u)&Int13DPT->iface_type[1], 'T');
        write_byte(DS, SI+(Bit16u)&Int13DPT->iface_type[2], 'A');
        write_byte(DS, SI+(Bit16u)&Int13DPT->iface_type[3], 0);

        if (iface==ATA_IFACE_ISA) {
          write_word(DS, SI+(Bit16u)&Int13DPT->iface_path[0], iobase1);
          write_word(DS, SI+(Bit16u)&Int13DPT->iface_path[2], 0);
          write_dword(DS, SI+(Bit16u)&Int13DPT->iface_path[4], 0L);
          }
        else { 
          // FIXME PCI
          }
        write_byte(DS, SI+(Bit16u)&Int13DPT->device_path[0], device%2);
        write_byte(DS, SI+(Bit16u)&Int13DPT->device_path[1], 0);
        write_word(DS, SI+(Bit16u)&Int13DPT->device_path[2], 0);
        write_dword(DS, SI+(Bit16u)&Int13DPT->device_path[4], 0L);

        checksum=0;
        for (i=30; i<64; i++) checksum+=read_byte(DS, SI + i);
        checksum = ~checksum;
        write_byte(DS, SI+(Bit16u)&Int13DPT->checksum, checksum);
        }

      goto int13_success;
      break;

    case 0x4e: // // IBM/MS set hardware configuration
      // DMA, prefetch, PIO maximum not supported
      switch (GET_AL()) {
        case 0x01:
        case 0x03:
        case 0x04:
        case 0x06:
          goto int13_success;
          break;
        default :
          goto int13_fail;
        }
      break;

    case 0x09: /* initialize drive parameters */
    case 0x0c: /* seek to specified cylinder */
    case 0x0d: /* alternate disk reset */
    case 0x11: /* recalibrate */
    case 0x14: /* controller internal diagnostic */
      BX_INFO("int13h_harddisk function %02xh unimplemented, returns success\n", GET_AH());
      goto int13_success;
      break;

    case 0x0a: /* read disk sectors with ECC */
    case 0x0b: /* write disk sectors with ECC */
    case 0x18: // set media type for format
    case 0x50: // IBM/MS send packet command
    default:
      BX_INFO("int13_harddisk function %02xh unsupported, returns fail\n", GET_AH());
      goto int13_fail;
      break;
    }

int13_fail:
    SET_AH(0x01); // defaults to invalid function in AH or invalid parameter
int13_fail_noah:
    SET_DISK_RET_STATUS(GET_AH());
int13_fail_nostatus:
    SET_CF();     // error occurred
    return;

int13_success:
    SET_AH(0x00); // no error
int13_success_noah:
    SET_DISK_RET_STATUS(0x00);
    CLEAR_CF();   // no error
    return;
}

// ---------------------------------------------------------------------------
// Start of int13 for cdrom
// ---------------------------------------------------------------------------

  void
int13_cdrom(EHBX, DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS)
  Bit16u EHBX, DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS;
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit8u  device, status, locks;
  Bit8u  atacmd[12];
  Bit32u lba;
  Bit16u count, segment, offset, i, size;

  BX_DEBUG_INT13_CD("int13_cdrom: AX=%04x BX=%04x CX=%04x DX=%04x ES=%04x\n", AX, BX, CX, DX, ES);
  // BX_DEBUG_INT13_CD("int13_cdrom: SS=%04x DS=%04x ES=%04x DI=%04x SI=%04x\n",get_SS(), DS, ES, DI, SI);
  
  SET_DISK_RET_STATUS(0x00);

  /* basic check : device should be 0xE0+ */
  if( (GET_ELDL() < 0xE0) || (GET_ELDL() >= 0xE0+BX_MAX_ATA_DEVICES) ) {
    BX_INFO("int13_cdrom: function %02x, ELDL out of range %02x\n", GET_AH(), GET_ELDL());
    goto int13_fail;
    }

  // Get the ata channel
  device=read_byte(ebda_seg,&EbdaData->ata.cdidmap[GET_ELDL()-0xE0]);

  /* basic check : device has to be valid  */
  if (device >= BX_MAX_ATA_DEVICES) {
    BX_INFO("int13_cdrom: function %02x, unmapped device for ELDL=%02x\n", GET_AH(), GET_ELDL());
    goto int13_fail;
    }
  
  switch (GET_AH()) {

    // all those functions return SUCCESS
    case 0x00: /* disk controller reset */
    case 0x09: /* initialize drive parameters */
    case 0x0c: /* seek to specified cylinder */
    case 0x0d: /* alternate disk reset */  
    case 0x10: /* check drive ready */    
    case 0x11: /* recalibrate */      
    case 0x14: /* controller internal diagnostic */
    case 0x16: /* detect disk change */
      goto int13_success;
      break;

    // all those functions return disk write-protected
    case 0x03: /* write disk sectors */
    case 0x05: /* format disk track */
    case 0x43: // IBM/MS extended write
      SET_AH(0x03);
      goto int13_fail_noah;
      break;

    case 0x01: /* read disk status */
      status = read_byte(0x0040, 0x0074);
      SET_AH(status);
      SET_DISK_RET_STATUS(0);

      /* set CF if error status read */
      if (status) goto int13_fail_nostatus;
      else        goto int13_success_noah;
      break;      

    case 0x15: /* read disk drive size */
      SET_AH(0x02);
      goto int13_fail_noah;
      break;

    case 0x41: // IBM/MS installation check
      BX=0xaa55;     // install check
      SET_AH(0x30);  // EDD 2.1
      CX=0x0007;     // ext disk access, removable and edd
      goto int13_success_noah;
      break;

    case 0x42: // IBM/MS extended read
    case 0x44: // IBM/MS verify sectors
    case 0x47: // IBM/MS extended seek
       
      count=read_word(DS, SI+(Bit16u)&Int13Ext->count);
      segment=read_word(DS, SI+(Bit16u)&Int13Ext->segment);
      offset=read_word(DS, SI+(Bit16u)&Int13Ext->offset);
 
      // Can't use 64 bits lba
      lba=read_dword(DS, SI+(Bit16u)&Int13Ext->lba2);
      if (lba != 0L) {
        BX_PANIC("int13_cdrom: function %02x. Can't use 64bits lba\n",GET_AH());
        goto int13_fail;
        }

      // Get 32 bits lba 
      lba=read_dword(DS, SI+(Bit16u)&Int13Ext->lba1);

      // If verify or seek
      if (( GET_AH() == 0x44 ) || ( GET_AH() == 0x47 ))
        goto int13_success;
      
      memsetb(get_SS(),atacmd,0,12);
      atacmd[0]=0x28;                      // READ command
      atacmd[7]=(count & 0xff00) >> 8;     // Sectors
      atacmd[8]=(count & 0x00ff);          // Sectors
      atacmd[2]=(lba & 0xff000000) >> 24;  // LBA
      atacmd[3]=(lba & 0x00ff0000) >> 16;
      atacmd[4]=(lba & 0x0000ff00) >> 8;
      atacmd[5]=(lba & 0x000000ff);
      status = ata_cmd_packet(device, 12, get_SS(), atacmd, 0, count*2048L, ATA_DATA_IN, segment,offset); 

      count = (Bit16u)(read_dword(ebda_seg, &EbdaData->ata.trsfbytes) >> 11);
      write_word(DS, SI+(Bit16u)&Int13Ext->count, count);

      if (status != 0) {
        BX_INFO("int13_cdrom: function %02x, status %02x !\n",GET_AH(),status);
        SET_AH(0x0c);
        goto int13_fail_noah;
        }

      goto int13_success;
      break;

    case 0x45: // IBM/MS lock/unlock drive
      if (GET_AL() > 2) goto int13_fail;

      locks = read_byte(ebda_seg, &EbdaData->ata.devices[device].lock);

      switch (GET_AL()) {
        case 0 :  // lock
          if (locks == 0xff) {
            SET_AH(0xb4);
            SET_AL(1);
            goto int13_fail_noah;
            }
          write_byte(ebda_seg, &EbdaData->ata.devices[device].lock, ++locks);
          SET_AL(1);
          break;
        case 1 :  // unlock
          if (locks == 0x00) {
            SET_AH(0xb0);
            SET_AL(0);
            goto int13_fail_noah;
            }
          write_byte(ebda_seg, &EbdaData->ata.devices[device].lock, --locks);
          SET_AL(locks==0?0:1);
          break;
        case 2 :  // status
          SET_AL(locks==0?0:1);
          break;
        }
      goto int13_success;
      break;

    case 0x46: // IBM/MS eject media
      locks = read_byte(ebda_seg, &EbdaData->ata.devices[device].lock);
      
      if (locks != 0) {
        SET_AH(0xb1); // media locked
        goto int13_fail_noah;
        }
      // FIXME should handle 0x31 no media in device
      // FIXME should handle 0xb5 valid request failed
    
      // Call removable media eject
      ASM_START
        push bp
        mov  bp, sp

        mov ah, #0x52
        int 15
        mov _int13_cdrom.status + 2[bp], ah
        jnc int13_cdrom_rme_end
        mov _int13_cdrom.status, #1
int13_cdrom_rme_end:
        pop bp
      ASM_END

      if (status != 0) {
        SET_AH(0xb1); // media locked
        goto int13_fail_noah;
      }

      goto int13_success;
      break;

    case 0x48: // IBM/MS get drive parameters
      size = read_word(DS,SI+(Bit16u)&Int13Ext->size);

      // Buffer is too small
      if(size < 0x1a) 
        goto int13_fail;

      // EDD 1.x
      if(size >= 0x1a) {
        Bit16u   cylinders, heads, spt, blksize;

        blksize   = read_word(ebda_seg, &EbdaData->ata.devices[device].blksize);

        write_word(DS, SI+(Bit16u)&Int13DPT->size, 0x1a);
        write_word(DS, SI+(Bit16u)&Int13DPT->infos, 0x74); // removable, media change, lockable, max values
        write_dword(DS, SI+(Bit16u)&Int13DPT->cylinders, 0xffffffff);
        write_dword(DS, SI+(Bit16u)&Int13DPT->heads, 0xffffffff);
        write_dword(DS, SI+(Bit16u)&Int13DPT->spt, 0xffffffff);
        write_dword(DS, SI+(Bit16u)&Int13DPT->sector_count1, 0xffffffff);  // FIXME should be Bit64
        write_dword(DS, SI+(Bit16u)&Int13DPT->sector_count2, 0xffffffff);  
        write_word(DS, SI+(Bit16u)&Int13DPT->blksize, blksize);  
        }

      // EDD 2.x
      if(size >= 0x1e) {
        Bit8u  channel, dev, irq, mode, checksum, i;
        Bit16u iobase1, iobase2, options;

        write_word(DS, SI+(Bit16u)&Int13DPT->size, 0x1e);

        write_word(DS, SI+(Bit16u)&Int13DPT->dpte_segment, ebda_seg);  
        write_word(DS, SI+(Bit16u)&Int13DPT->dpte_offset, &EbdaData->ata.dpte);  

        // Fill in dpte
        channel = device / 2;
        iobase1 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase1);
        iobase2 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase2);
        irq = read_byte(ebda_seg, &EbdaData->ata.channels[channel].irq);
        mode = read_byte(ebda_seg, &EbdaData->ata.devices[device].mode);

        // FIXME atapi device
        options  = (1<<4); // lba translation
        options |= (1<<5); // removable device
        options |= (1<<6); // atapi device
        options |= (mode==ATA_MODE_PIO32?1:0<<7);

        write_word(ebda_seg, &EbdaData->ata.dpte.iobase1, iobase1);
        write_word(ebda_seg, &EbdaData->ata.dpte.iobase2, iobase2);
        write_byte(ebda_seg, &EbdaData->ata.dpte.prefix, (0xe | (device % 2))<<4 );
        write_byte(ebda_seg, &EbdaData->ata.dpte.unused, 0xcb );
        write_byte(ebda_seg, &EbdaData->ata.dpte.irq, irq );
        write_byte(ebda_seg, &EbdaData->ata.dpte.blkcount, 1 );
        write_byte(ebda_seg, &EbdaData->ata.dpte.dma, 0 );
        write_byte(ebda_seg, &EbdaData->ata.dpte.pio, 0 );
        write_word(ebda_seg, &EbdaData->ata.dpte.options, options);
        write_word(ebda_seg, &EbdaData->ata.dpte.reserved, 0);
        write_byte(ebda_seg, &EbdaData->ata.dpte.revision, 0x11);

        checksum=0;
        for (i=0; i<15; i++) checksum+=read_byte(ebda_seg, (&EbdaData->ata.dpte) + i);
        checksum = ~checksum;
        write_byte(ebda_seg, &EbdaData->ata.dpte.checksum, checksum);
        }

      // EDD 3.x
      if(size >= 0x42) {
        Bit8u channel, iface, checksum, i;
        Bit16u iobase1;

        channel = device / 2;
        iface = read_byte(ebda_seg, &EbdaData->ata.channels[channel].iface);
        iobase1 = read_word(ebda_seg, &EbdaData->ata.channels[channel].iobase1);

        write_word(DS, SI+(Bit16u)&Int13DPT->size, 0x42);
        write_word(DS, SI+(Bit16u)&Int13DPT->key, 0xbedd);
        write_byte(DS, SI+(Bit16u)&Int13DPT->dpi_length, 0x24);
        write_byte(DS, SI+(Bit16u)&Int13DPT->reserved1, 0);
        write_word(DS, SI+(Bit16u)&Int13DPT->reserved2, 0);

        if (iface==ATA_IFACE_ISA) {
          write_byte(DS, SI+(Bit16u)&Int13DPT->host_bus[0], 'I');
          write_byte(DS, SI+(Bit16u)&Int13DPT->host_bus[1], 'S');
          write_byte(DS, SI+(Bit16u)&Int13DPT->host_bus[2], 'A');
          write_byte(DS, SI+(Bit16u)&Int13DPT->host_bus[3], 0);
          }
        else { 
          // FIXME PCI
          }
        write_byte(DS, SI+(Bit16u)&Int13DPT->iface_type[0], 'A');
        write_byte(DS, SI+(Bit16u)&Int13DPT->iface_type[1], 'T');
        write_byte(DS, SI+(Bit16u)&Int13DPT->iface_type[2], 'A');
        write_byte(DS, SI+(Bit16u)&Int13DPT->iface_type[3], 0);

        if (iface==ATA_IFACE_ISA) {
          write_word(DS, SI+(Bit16u)&Int13DPT->iface_path[0], iobase1);
          write_word(DS, SI+(Bit16u)&Int13DPT->iface_path[2], 0);
          write_dword(DS, SI+(Bit16u)&Int13DPT->iface_path[4], 0L);
          }
        else { 
          // FIXME PCI
          }
        write_byte(DS, SI+(Bit16u)&Int13DPT->device_path[0], device%2);
        write_byte(DS, SI+(Bit16u)&Int13DPT->device_path[1], 0);
        write_word(DS, SI+(Bit16u)&Int13DPT->device_path[2], 0);
        write_dword(DS, SI+(Bit16u)&Int13DPT->device_path[4], 0L);

        checksum=0;
        for (i=30; i<64; i++) checksum+=read_byte(DS, SI + i);
        checksum = ~checksum;
        write_byte(DS, SI+(Bit16u)&Int13DPT->checksum, checksum);
        }

      goto int13_success;
      break;

    case 0x49: // IBM/MS extended media change
      // always send changed ??
      SET_AH(06);
      goto int13_fail_nostatus;
      break;
      
    case 0x4e: // // IBM/MS set hardware configuration
      // DMA, prefetch, PIO maximum not supported
      switch (GET_AL()) {
        case 0x01:
        case 0x03:
        case 0x04:
        case 0x06:
          goto int13_success;
          break;
        default :
          goto int13_fail;
        }
      break;

    // all those functions return unimplemented
    case 0x02: /* read sectors */
    case 0x04: /* verify sectors */
    case 0x08: /* read disk drive parameters */
    case 0x0a: /* read disk sectors with ECC */
    case 0x0b: /* write disk sectors with ECC */
    case 0x18: /* set media type for format */
    case 0x50: // ? - send packet command
    default:
      BX_INFO("int13_cdrom: unsupported AH=%02x\n", GET_AH());
      goto int13_fail;
      break;
    }

int13_fail:
    SET_AH(0x01); // defaults to invalid function in AH or invalid parameter
int13_fail_noah:
    SET_DISK_RET_STATUS(GET_AH());
int13_fail_nostatus:
    SET_CF();     // error occurred
    return;

int13_success:
    SET_AH(0x00); // no error
int13_success_noah:
    SET_DISK_RET_STATUS(0x00);
    CLEAR_CF();   // no error
    return;
}

// ---------------------------------------------------------------------------
// End of int13 for cdrom
// ---------------------------------------------------------------------------

#if BX_ELTORITO_BOOT
// ---------------------------------------------------------------------------
// Start of int13 for eltorito functions
// ---------------------------------------------------------------------------

  void
int13_eltorito(DS, ES, DI, SI, BP, SP, BX, DX, CX, AX, IP, CS, FLAGS)
  Bit16u DS, ES, DI, SI, BP, SP, BX, DX, CX, AX, IP, CS, FLAGS;
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);

  BX_DEBUG_INT13_ET("int13_eltorito: AX=%04x BX=%04x CX=%04x DX=%04x ES=%04x\n", AX, BX, CX, DX, ES);
  // BX_DEBUG_INT13_ET("int13_eltorito: SS=%04x DS=%04x ES=%04x DI=%04x SI=%04x\n",get_SS(), DS, ES, DI, SI);
  
  switch (GET_AH()) {

    // FIXME ElTorito Various. Should be implemented
    case 0x4a: // ElTorito - Initiate disk emu
    case 0x4c: // ElTorito - Initiate disk emu and boot
    case 0x4d: // ElTorito - Return Boot catalog
      BX_PANIC("Int13 eltorito call with AX=%04x. Please report\n",AX);
      goto int13_fail;
      break;

    case 0x4b: // ElTorito - Terminate disk emu
      // FIXME ElTorito Hardcoded
      write_byte(DS,SI+0x00,0x13);
      write_byte(DS,SI+0x01,read_byte(ebda_seg,&EbdaData->cdemu.media));
      write_byte(DS,SI+0x02,read_byte(ebda_seg,&EbdaData->cdemu.emulated_drive));
      write_byte(DS,SI+0x03,read_byte(ebda_seg,&EbdaData->cdemu.controller_index));
      write_dword(DS,SI+0x04,read_dword(ebda_seg,&EbdaData->cdemu.ilba));
      write_word(DS,SI+0x08,read_word(ebda_seg,&EbdaData->cdemu.device_spec));
      write_word(DS,SI+0x0a,read_word(ebda_seg,&EbdaData->cdemu.buffer_segment));
      write_word(DS,SI+0x0c,read_word(ebda_seg,&EbdaData->cdemu.load_segment));
      write_word(DS,SI+0x0e,read_word(ebda_seg,&EbdaData->cdemu.sector_count));
      write_byte(DS,SI+0x10,read_byte(ebda_seg,&EbdaData->cdemu.vdevice.cylinders));
      write_byte(DS,SI+0x11,read_byte(ebda_seg,&EbdaData->cdemu.vdevice.spt));
      write_byte(DS,SI+0x12,read_byte(ebda_seg,&EbdaData->cdemu.vdevice.heads));

      // If we have to terminate emulation
      if(GET_AL() == 0x00) {
        // FIXME ElTorito Various. Should be handled accordingly to spec
        write_byte(ebda_seg,&EbdaData->cdemu.active, 0x00); // bye bye
        }

      goto int13_success;
      break;

    default:
      BX_INFO("int13_eltorito: unsupported AH=%02x\n", GET_AH());
      goto int13_fail;
      break;
    }

int13_fail:
    SET_AH(0x01); // defaults to invalid function in AH or invalid parameter
    SET_DISK_RET_STATUS(GET_AH());
    SET_CF();     // error occurred
    return;

int13_success:
    SET_AH(0x00); // no error
    SET_DISK_RET_STATUS(0x00);
    CLEAR_CF();   // no error
    return;
}

// ---------------------------------------------------------------------------
// End of int13 for eltorito functions
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// Start of int13 when emulating a device from the cd
// ---------------------------------------------------------------------------

  void
int13_cdemu(DS, ES, DI, SI, BP, SP, BX, DX, CX, AX, IP, CS, FLAGS)
  Bit16u DS, ES, DI, SI, BP, SP, BX, DX, CX, AX, IP, CS, FLAGS;
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit8u  device, status;
  Bit16u vheads, vspt, vcylinders;
  Bit16u head, sector, cylinder, nbsectors;
  Bit32u vlba, ilba, slba, elba;
  Bit16u before, segment, offset;
  Bit8u  atacmd[12];

  BX_DEBUG_INT13_ET("int13_cdemu: AX=%04x BX=%04x CX=%04x DX=%04x ES=%04x\n", AX, BX, CX, DX, ES);
  //BX_DEBUG_INT13_ET("int13_cdemu: SS=%04x ES=%04x DI=%04x SI=%04x\n", get_SS(), ES, DI, SI);
  
  /* at this point, we are emulating a floppy/harddisk */
  
  // Recompute the device number 
  device  = read_byte(ebda_seg,&EbdaData->cdemu.controller_index) * 2;
  device += read_byte(ebda_seg,&EbdaData->cdemu.device_spec);

  SET_DISK_RET_STATUS(0x00);

  /* basic checks : emulation should be active, dl should equal the emulated drive */
  if( (read_byte(ebda_seg,&EbdaData->cdemu.active) ==0 )
   || (read_byte(ebda_seg,&EbdaData->cdemu.emulated_drive ) != GET_DL())) {
    BX_INFO("int13_cdemu: function %02x, emulation not active for DL= %02x\n", GET_AH(), GET_DL());
    goto int13_fail;
    }

  
  switch (GET_AH()) {

    // all those functions return SUCCESS
    case 0x00: /* disk controller reset */
    case 0x09: /* initialize drive parameters */
    case 0x0c: /* seek to specified cylinder */
    case 0x0d: /* alternate disk reset */  // FIXME ElTorito Various. should really reset ?
    case 0x10: /* check drive ready */     // FIXME ElTorito Various. should check if ready ?
    case 0x11: /* recalibrate */      
    case 0x14: /* controller internal diagnostic */
    case 0x16: /* detect disk change */
      goto int13_success;
      break;

    // all those functions return disk write-protected
    case 0x03: /* write disk sectors */
    case 0x05: /* format disk track */
      SET_AH(0x03);
      goto int13_fail_noah;
      break;

    case 0x01: /* read disk status */
      status=read_byte(0x0040, 0x0074);
      SET_AH(status);
      SET_DISK_RET_STATUS(0);

      /* set CF if error status read */
      if (status) goto int13_fail_nostatus;
      else        goto int13_success_noah;
      break;

    case 0x02: // read disk sectors
    case 0x04: // verify disk sectors
      vspt       = read_word(ebda_seg,&EbdaData->cdemu.vdevice.spt); 
      vcylinders = read_word(ebda_seg,&EbdaData->cdemu.vdevice.cylinders); 
      vheads     = read_word(ebda_seg,&EbdaData->cdemu.vdevice.heads); 

      ilba       = read_dword(ebda_seg,&EbdaData->cdemu.ilba);

      sector    = GET_CL() & 0x003f;
      cylinder  = (GET_CL() & 0x00c0) << 2 | GET_CH();
      head      = GET_DH();
      nbsectors = GET_AL();
      segment   = ES;
      offset    = BX;

      // no sector to read ?
      if(nbsectors==0) goto int13_success;

      // sanity checks sco openserver needs this!
      if ((sector   >  vspt)
       || (cylinder >= vcylinders)
       || (head     >= vheads)) {
        goto int13_fail;
        }

      // After controls, verify do nothing
      if (GET_AH() == 0x04) goto int13_success;

      segment = ES+(BX / 16);
      offset  = BX % 16;

      // calculate the virtual lba inside the image
      vlba=((((Bit32u)cylinder*(Bit32u)vheads)+(Bit32u)head)*(Bit32u)vspt)+((Bit32u)(sector-1));
 
      // In advance so we don't loose the count
      SET_AL(nbsectors);

      // start lba on cd
      slba  = (Bit32u)vlba/4; 
      before= (Bit16u)vlba%4;

      // end lba on cd
      elba = (Bit32u)(vlba+nbsectors-1)/4;
      
      memsetb(get_SS(),atacmd,0,12);
      atacmd[0]=0x28;                      // READ command
      atacmd[7]=((Bit16u)(elba-slba+1) & 0xff00) >> 8; // Sectors
      atacmd[8]=((Bit16u)(elba-slba+1) & 0x00ff);      // Sectors
      atacmd[2]=(ilba+slba & 0xff000000) >> 24;  // LBA
      atacmd[3]=(ilba+slba & 0x00ff0000) >> 16;
      atacmd[4]=(ilba+slba & 0x0000ff00) >> 8;
      atacmd[5]=(ilba+slba & 0x000000ff);
      if((status = ata_cmd_packet(device, 12, get_SS(), atacmd, before*512, nbsectors*512L, ATA_DATA_IN, segment,offset)) != 0) {
        BX_INFO("int13_cdemu: function %02x, error %02x !\n",GET_AH(),status);
        SET_AH(0x02);
        SET_AL(0);
        goto int13_fail_noah;
        }

      goto int13_success;
      break;

    case 0x08: /* read disk drive parameters */
      vspt=read_word(ebda_seg,&EbdaData->cdemu.vdevice.spt); 
      vcylinders=read_word(ebda_seg,&EbdaData->cdemu.vdevice.cylinders) - 1; 
      vheads=read_word(ebda_seg,&EbdaData->cdemu.vdevice.heads) - 1; 
 
      SET_AL( 0x00 );
      SET_BL( 0x00 );
      SET_CH( vcylinders & 0xff );
      SET_CL((( vcylinders >> 2) & 0xc0) | ( vspt  & 0x3f ));
      SET_DH( vheads );
      SET_DL( 0x02 );   // FIXME ElTorito Various. should send the real count of drives 1 or 2
                        // FIXME ElTorito Harddisk. should send the HD count
 
      switch(read_byte(ebda_seg,&EbdaData->cdemu.media)) {
        case 0x01: SET_BL( 0x02 ); break;
        case 0x02: SET_BL( 0x04 ); break;
        case 0x03: SET_BL( 0x06 ); break;
        }

ASM_START
      push bp
      mov  bp, sp
      mov ax, #diskette_param_table2
      mov _int13_cdemu.DI+2[bp], ax
      mov _int13_cdemu.ES+2[bp], cs
      pop  bp
ASM_END
      goto int13_success;
      break;

    case 0x15: /* read disk drive size */
      // FIXME ElTorito Harddisk. What geometry to send ?
      SET_AH(0x03);
      goto int13_success_noah;
      break;

    // all those functions return unimplemented
    case 0x0a: /* read disk sectors with ECC */
    case 0x0b: /* write disk sectors with ECC */
    case 0x18: /* set media type for format */
    case 0x41: // IBM/MS installation check
      // FIXME ElTorito Harddisk. Darwin would like to use EDD
    case 0x42: // IBM/MS extended read
    case 0x43: // IBM/MS extended write
    case 0x44: // IBM/MS verify sectors
    case 0x45: // IBM/MS lock/unlock drive
    case 0x46: // IBM/MS eject media
    case 0x47: // IBM/MS extended seek
    case 0x48: // IBM/MS get drive parameters 
    case 0x49: // IBM/MS extended media change
    case 0x4e: // ? - set hardware configuration
    case 0x50: // ? - send packet command
    default:
      BX_INFO("int13_cdemu function AH=%02x unsupported, returns fail\n", GET_AH());
      goto int13_fail;
      break;
    }

int13_fail:
    SET_AH(0x01); // defaults to invalid function in AH or invalid parameter
int13_fail_noah:
    SET_DISK_RET_STATUS(GET_AH());
int13_fail_nostatus:
    SET_CF();     // error occurred
    return;

int13_success:
    SET_AH(0x00); // no error
int13_success_noah:
    SET_DISK_RET_STATUS(0x00);
    CLEAR_CF();   // no error
    return;
}

// ---------------------------------------------------------------------------
// End of int13 when emulating a device from the cd
// ---------------------------------------------------------------------------

#endif // BX_ELTORITO_BOOT

#else //BX_USE_ATADRV

  void
outLBA(cylinder,hd_heads,head,hd_sectors,sector,dl)
  Bit16u cylinder;
  Bit16u hd_heads;
  Bit16u head;
  Bit16u hd_sectors;
  Bit16u sector;
  Bit16u dl;
{
ASM_START
        push   bp
        mov    bp, sp
        push   eax
        push   ebx
        push   edx
        xor    eax,eax
        mov    ax,4[bp]  // cylinder
        xor    ebx,ebx
        mov    bl,6[bp]  // hd_heads
        imul   ebx

        mov    bl,8[bp]  // head
        add    eax,ebx
        mov    bl,10[bp] // hd_sectors
        imul   ebx
        mov    bl,12[bp] // sector
        add    eax,ebx

        dec    eax
        mov    dx,#0x1f3
        out    dx,al
        mov    dx,#0x1f4
        mov    al,ah
        out    dx,al
        shr    eax,#16
        mov    dx,#0x1f5
        out    dx,al
        and    ah,#0xf
        mov    bl,14[bp] // dl
        and    bl,#1
        shl    bl,#4
        or     ah,bl
        or     ah,#0xe0
        mov    al,ah
        mov    dx,#0x01f6
        out    dx,al
        pop    edx
        pop    ebx
        pop    eax
        pop    bp
ASM_END
}

  void
int13_harddisk(DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS)
  Bit16u DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS;
{
  Bit8u    drive, num_sectors, sector, head, status, mod;
  Bit8u    drive_map;
  Bit8u    n_drives;
  Bit16u   cyl_mod, ax;
  Bit16u   max_cylinder, cylinder, total_sectors;
  Bit16u   hd_cylinders;
  Bit8u    hd_heads, hd_sectors;
  Bit16u   val16;
  Bit8u    sector_count;
  unsigned int i;
  Bit16u   tempbx;
  Bit16u   dpsize;

  Bit16u   count, segment, offset;
  Bit32u   lba;
  Bit16u   error;

  BX_DEBUG_INT13_HD("int13 harddisk: AX=%04x BX=%04x CX=%04x DX=%04x ES=%04x\n", AX, BX, CX, DX, ES);

  write_byte(0x0040, 0x008e, 0);  // clear completion flag

  /* at this point, DL is >= 0x80 to be passed from the floppy int13h
     handler code */
  /* check how many disks first (cmos reg 0x12), return an error if
     drive not present */
  drive_map = inb_cmos(0x12);
  drive_map = (((drive_map & 0xf0)==0) ? 0 : 1) |
              (((drive_map & 0x0f)==0) ? 0 : 2);
  n_drives = (drive_map==0) ? 0 :
    ((drive_map==3) ? 2 : 1);

  if (!(drive_map & (1<<(GET_ELDL()&0x7f)))) { /* allow 0, 1, or 2 disks */
    SET_AH(0x01);
    SET_DISK_RET_STATUS(0x01);
    SET_CF(); /* error occurred */
    return;
    }

  switch (GET_AH()) {

    case 0x00: /* disk controller reset */
BX_DEBUG_INT13_HD("int13_f00\n");

      SET_AH(0);
      SET_DISK_RET_STATUS(0);
      set_diskette_ret_status(0);
      set_diskette_current_cyl(0, 0); /* current cylinder, diskette 1 */
      set_diskette_current_cyl(1, 0); /* current cylinder, diskette 2 */
      CLEAR_CF(); /* successful */
      return;
      break;

    case 0x01: /* read disk status */
BX_DEBUG_INT13_HD("int13_f01\n");
      status = read_byte(0x0040, 0x0074);
      SET_AH(status);
      SET_DISK_RET_STATUS(0);
      /* set CF if error status read */
      if (status) SET_CF();
      else        CLEAR_CF();
      return;
      break;

    case 0x04: // verify disk sectors
    case 0x02: // read disk sectors
      drive = GET_ELDL();
      get_hd_geometry(drive, &hd_cylinders, &hd_heads, &hd_sectors);

      num_sectors = GET_AL();
      cylinder    = (GET_CL() & 0x00c0) << 2 | GET_CH();
      sector      = (GET_CL() & 0x3f);
      head        = GET_DH();


      if (hd_cylinders > 1024) {
        if (hd_cylinders <= 2048) {
          cylinder <<= 1;
          }
        else if (hd_cylinders <= 4096) {
          cylinder <<= 2;
          }
        else if (hd_cylinders <= 8192) {
          cylinder <<= 3;
          }
        else { // hd_cylinders <= 16384
          cylinder <<= 4;
          }

        ax = head / hd_heads;
        cyl_mod = ax & 0xff;
        head    = ax >> 8;
        cylinder |= cyl_mod;
        }

      if ( (cylinder >= hd_cylinders) ||
           (sector > hd_sectors) ||
           (head >= hd_heads) ) {
        SET_AH(1);
        SET_DISK_RET_STATUS(1);
        SET_CF(); /* error occurred */
        return;
        }

      if ( (num_sectors > 128) || (num_sectors == 0) )
        BX_PANIC("int13_harddisk(): num_sectors out of range!\n");

      if (head > 15)
        BX_PANIC("hard drive BIOS:(read/verify) head > 15\n");

      if ( GET_AH() == 0x04 ) {
        SET_AH(0);
        SET_DISK_RET_STATUS(0);
        CLEAR_CF();
        return;
        }

      status = inb(0x1f7);
      if (status & 0x80) {
        BX_PANIC("hard drive BIOS:(read/verify) BUSY bit set\n");
        }
      outb(0x01f2, num_sectors);
      /* activate LBA? (tomv) */
      if (hd_heads > 16) {
BX_DEBUG_INT13_HD("CHS: %x %x %x\n", cylinder, head, sector);
        outLBA(cylinder,hd_heads,head,hd_sectors,sector,drive);
        }
      else {
        outb(0x01f3, sector);
        outb(0x01f4, cylinder & 0x00ff);
        outb(0x01f5, cylinder >> 8);
        outb(0x01f6, 0xa0 | ((drive & 0x01)<<4) | (head & 0x0f));
        }
      outb(0x01f7, 0x20);

      while (1) {
        status = inb(0x1f7);
        if ( !(status & 0x80) ) break;
        }

      if (status & 0x01) {
        BX_PANIC("hard drive BIOS:(read/verify) read error\n");
      } else if ( !(status & 0x08) ) {
        BX_DEBUG_INT13_HD("status was %02x\n", (unsigned) status);
        BX_PANIC("hard drive BIOS:(read/verify) expected DRQ=1\n");
      }

      sector_count = 0;
      tempbx = BX;

ASM_START
  sti  ;; enable higher priority interrupts
ASM_END

      while (1) {
ASM_START
        ;; store temp bx in real DI register
        push bp
        mov  bp, sp
        mov  di, _int13_harddisk.tempbx + 2 [bp]
        pop  bp

        ;; adjust if there will be an overrun
        cmp   di, #0xfe00
        jbe   i13_f02_no_adjust
i13_f02_adjust:
        sub   di, #0x0200 ; sub 512 bytes from offset
        mov   ax, es
        add   ax, #0x0020 ; add 512 to segment
        mov   es, ax

i13_f02_no_adjust:
        mov  cx, #0x0100   ;; counter (256 words = 512b)
        mov  dx, #0x01f0  ;; AT data read port

        rep
          insw ;; CX words transfered from port(DX) to ES:[DI]

i13_f02_done:
        ;; store real DI register back to temp bx
        push bp
        mov  bp, sp
        mov  _int13_harddisk.tempbx + 2 [bp], di
        pop  bp
ASM_END

        sector_count++;
        num_sectors--;
        if (num_sectors == 0) {
          status = inb(0x1f7);
          if ( (status & 0xc9) != 0x40 )
            BX_PANIC("no sectors left to read/verify, status is %02x\n", (unsigned) status);
          break;
          }
        else {
          status = inb(0x1f7);
          if ( (status & 0xc9) != 0x48 )
            BX_PANIC("more sectors left to read/verify, status is %02x\n", (unsigned) status);
          continue;
          }
        }

      SET_AH(0);
      SET_DISK_RET_STATUS(0);
      SET_AL(sector_count);
      CLEAR_CF(); /* successful */
      return;
      break;


    case 0x03: /* write disk sectors */
BX_DEBUG_INT13_HD("int13_f03\n");
      drive = GET_ELDL ();
      get_hd_geometry(drive, &hd_cylinders, &hd_heads, &hd_sectors);

      num_sectors = GET_AL();
      cylinder    = GET_CH();
      cylinder    |= ( ((Bit16u) GET_CL()) << 2) & 0x300;
      sector      = (GET_CL() & 0x3f);
      head        = GET_DH();

      if (hd_cylinders > 1024) {
        if (hd_cylinders <= 2048) {
          cylinder <<= 1;
          }
        else if (hd_cylinders <= 4096) {
          cylinder <<= 2;
          }
        else if (hd_cylinders <= 8192) {
          cylinder <<= 3;
          }
        else { // hd_cylinders <= 16384
          cylinder <<= 4;
          }

        ax = head / hd_heads;
        cyl_mod = ax & 0xff;
        head    = ax >> 8;
        cylinder |= cyl_mod;
        }

      if ( (cylinder >= hd_cylinders) ||
           (sector > hd_sectors) ||
           (head >= hd_heads) ) {
        SET_AH( 1);
        SET_DISK_RET_STATUS(1);
        SET_CF(); /* error occurred */
        return;
        }

      if ( (num_sectors > 128) || (num_sectors == 0) )
        BX_PANIC("int13_harddisk(): num_sectors out of range!\n");

      if (head > 15)
        BX_PANIC("hard drive BIOS:(read) head > 15\n");

      status = inb(0x1f7);
      if (status & 0x80) {
        BX_PANIC("hard drive BIOS:(read) BUSY bit set\n");
        }
// should check for Drive Ready Bit also in status reg
      outb(0x01f2, num_sectors);

      /* activate LBA? (tomv) */
      if (hd_heads > 16) {
BX_DEBUG_INT13_HD("CHS (write): %x %x %x\n", cylinder, head, sector);
        outLBA(cylinder,hd_heads,head,hd_sectors,sector,GET_ELDL());
        }
      else {
        outb(0x01f3, sector);
        outb(0x01f4, cylinder & 0x00ff);
        outb(0x01f5, cylinder >> 8);
        outb(0x01f6, 0xa0 | ((GET_ELDL() & 0x01)<<4) | (head & 0x0f));
        }
      outb(0x01f7, 0x30);

      // wait for busy bit to turn off after seeking
      while (1) {
        status = inb(0x1f7);
        if ( !(status & 0x80) ) break;
        }

      if ( !(status & 0x08) ) {
        BX_DEBUG_INT13_HD("status was %02x\n", (unsigned) status);
        BX_PANIC("hard drive BIOS:(write) data-request bit not set\n");
        }

      sector_count = 0;
      tempbx = BX;

ASM_START
  sti  ;; enable higher priority interrupts
ASM_END

      while (1) {
ASM_START
        ;; store temp bx in real SI register
        push bp
        mov  bp, sp
        mov  si, _int13_harddisk.tempbx + 2 [bp]
        pop  bp

        ;; adjust if there will be an overrun
        cmp   si, #0xfe00
        jbe   i13_f03_no_adjust
i13_f03_adjust:
        sub   si, #0x0200 ; sub 512 bytes from offset
        mov   ax, es
        add   ax, #0x0020 ; add 512 to segment
        mov   es, ax

i13_f03_no_adjust:
        mov  cx, #0x0100   ;; counter (256 words = 512b)
        mov  dx, #0x01f0  ;; AT data read port

        seg ES
        rep
          outsw ;; CX words tranfered from ES:[SI] to port(DX)

        ;; store real SI register back to temp bx
        push bp
        mov  bp, sp
        mov  _int13_harddisk.tempbx + 2 [bp], si
        pop  bp
ASM_END

        sector_count++;
        num_sectors--;
        if (num_sectors == 0) {
          status = inb(0x1f7);
          if ( (status & 0xe9) != 0x40 )
            BX_PANIC("no sectors left to write, status is %02x\n", (unsigned) status);
          break;
          }
        else {
          status = inb(0x1f7);
          if ( (status & 0xc9) != 0x48 )
            BX_PANIC("more sectors left to write, status is %02x\n", (unsigned) status);
          continue;
          }
        }

      SET_AH(0);
      SET_DISK_RET_STATUS(0);
      SET_AL(sector_count);
      CLEAR_CF(); /* successful */
      return;
      break;

    case 0x05: /* format disk track */
BX_DEBUG_INT13_HD("int13_f05\n");
      BX_PANIC("format disk track called\n");
      /* nop */
      SET_AH(0);
      SET_DISK_RET_STATUS(0);
      CLEAR_CF(); /* successful */
      return;
      break;

    case 0x08: /* read disk drive parameters */
BX_DEBUG_INT13_HD("int13_f08\n");
      
      drive = GET_ELDL ();
      get_hd_geometry(drive, &hd_cylinders, &hd_heads, &hd_sectors);

      // translate CHS
      //
      if (hd_cylinders <= 1024) {
        // hd_cylinders >>= 0;
        // hd_heads <<= 0;
        }
      else if (hd_cylinders <= 2048) {
        hd_cylinders >>= 1;
        hd_heads <<= 1;
        }
      else if (hd_cylinders <= 4096) {
        hd_cylinders >>= 2;
        hd_heads <<= 2;
        }
      else if (hd_cylinders <= 8192) {
        hd_cylinders >>= 3;
        hd_heads <<= 3;
        }
      else { // hd_cylinders <= 16384
        hd_cylinders >>= 4;
        hd_heads <<= 4;
        }

      max_cylinder = hd_cylinders - 2; /* 0 based */
      SET_AL(0);
      SET_CH(max_cylinder & 0xff);
      SET_CL(((max_cylinder >> 2) & 0xc0) | (hd_sectors & 0x3f));
      SET_DH(hd_heads - 1);
      SET_DL(n_drives); /* returns 0, 1, or 2 hard drives */
      SET_AH(0);
      SET_DISK_RET_STATUS(0);
      CLEAR_CF(); /* successful */

      return;
      break;

    case 0x09: /* initialize drive parameters */
BX_DEBUG_INT13_HD("int13_f09\n");
      SET_AH(0);
      SET_DISK_RET_STATUS(0);
      CLEAR_CF(); /* successful */
      return;
      break;

    case 0x0a: /* read disk sectors with ECC */
BX_DEBUG_INT13_HD("int13_f0a\n");
    case 0x0b: /* write disk sectors with ECC */
BX_DEBUG_INT13_HD("int13_f0b\n");
      BX_PANIC("int13h Functions 0Ah & 0Bh not implemented!\n");
      return;
      break;

    case 0x0c: /* seek to specified cylinder */
BX_DEBUG_INT13_HD("int13_f0c\n");
      BX_INFO("int13h function 0ch (seek) not implemented!\n");
      SET_AH(0);
      SET_DISK_RET_STATUS(0);
      CLEAR_CF(); /* successful */
      return;
      break;

    case 0x0d: /* alternate disk reset */
BX_DEBUG_INT13_HD("int13_f0d\n");
      SET_AH(0);
      SET_DISK_RET_STATUS(0);
      CLEAR_CF(); /* successful */
      return;
      break;

    case 0x10: /* check drive ready */
BX_DEBUG_INT13_HD("int13_f10\n");
      //SET_AH(0);
      //SET_DISK_RET_STATUS(0);
      //CLEAR_CF(); /* successful */
      //return;
      //break;

      // should look at 40:8E also???
      status = inb(0x01f7);
      if ( (status & 0xc0) == 0x40 ) {
        SET_AH(0);
        SET_DISK_RET_STATUS(0);
        CLEAR_CF(); // drive ready
        return;
        }
      else {
        SET_AH(0xAA);
        SET_DISK_RET_STATUS(0xAA);
        SET_CF(); // not ready
        return;
        }
      break;

    case 0x11: /* recalibrate */
BX_DEBUG_INT13_HD("int13_f11\n");
      SET_AH(0);
      SET_DISK_RET_STATUS(0);
      CLEAR_CF(); /* successful */
      return;
      break;

    case 0x14: /* controller internal diagnostic */
BX_DEBUG_INT13_HD("int13_f14\n");
      SET_AH(0);
      SET_DISK_RET_STATUS(0);
      CLEAR_CF(); /* successful */
      SET_AL(0);
      return;
      break;

    case 0x15: /* read disk drive size */
      drive = GET_ELDL();
      get_hd_geometry(drive, &hd_cylinders, &hd_heads, &hd_sectors);
ASM_START
      push bp
      mov  bp, sp
      mov  al, _int13_harddisk.hd_heads + 2 [bp]
      mov  ah, _int13_harddisk.hd_sectors + 2 [bp]
      mul  al, ah ;; ax = heads * sectors
      mov  bx, _int13_harddisk.hd_cylinders + 2 [bp]
      dec  bx     ;; use (cylinders - 1) ???
      mul  ax, bx ;; dx:ax = (cylinders -1) * (heads * sectors)
      ;; now we need to move the 32bit result dx:ax to what the
      ;; BIOS wants which is cx:dx.
      ;; and then into CX:DX on the stack
      mov  _int13_harddisk.CX + 2 [bp], dx
      mov  _int13_harddisk.DX + 2 [bp], ax
      pop  bp
ASM_END
      SET_AH(3);  // hard disk accessible
      SET_DISK_RET_STATUS(0); // ??? should this be 0
      CLEAR_CF(); // successful
      return;
      break;

    case 0x18: // set media type for format
    case 0x41: // IBM/MS 
    case 0x42: // IBM/MS 
    case 0x43: // IBM/MS 
    case 0x44: // IBM/MS 
    case 0x45: // IBM/MS lock/unlock drive
    case 0x46: // IBM/MS eject media
    case 0x47: // IBM/MS extended seek
    case 0x49: // IBM/MS extended media change
    case 0x50: // IBM/MS send packet command
    default:
      BX_INFO("int13_harddisk: unsupported AH=%02x\n", GET_AH());

      SET_AH(1);  // code=invalid function in AH or invalid parameter
      SET_DISK_RET_STATUS(1);
      SET_CF(); /* unsuccessful */
      return;
      break;
    }
}

static char panic_msg_reg12h[] = "HD%d cmos reg 12h not type F\n";
static char panic_msg_reg19h[] = "HD%d cmos reg %02xh not user definable type 47\n";

  void
get_hd_geometry(drive, hd_cylinders, hd_heads, hd_sectors)
  Bit8u drive;
  Bit16u *hd_cylinders;
  Bit8u  *hd_heads;
  Bit8u  *hd_sectors;
{
  Bit8u hd_type;
  Bit16u ss;
  Bit16u cylinders;
  Bit8u iobase;

  ss = get_SS();
  if (drive == 0x80) {
    hd_type = inb_cmos(0x12) & 0xf0;
    if (hd_type != 0xf0)
      BX_INFO(panic_msg_reg12h,0);
    hd_type = inb_cmos(0x19); // HD0: extended type
    if (hd_type != 47)
      BX_INFO(panic_msg_reg19h,0,0x19);
    iobase = 0x1b;
  } else {
    hd_type = inb_cmos(0x12) & 0x0f;
    if (hd_type != 0x0f)
      BX_INFO(panic_msg_reg12h,1);
    hd_type = inb_cmos(0x1a); // HD0: extended type
    if (hd_type != 47)
      BX_INFO(panic_msg_reg19h,0,0x1a);
    iobase = 0x24;
  }

  // cylinders
  cylinders = inb_cmos(iobase) | (inb_cmos(iobase+1) << 8);
  write_word(ss, hd_cylinders, cylinders);

  // heads
  write_byte(ss, hd_heads, inb_cmos(iobase+2));

  // sectors per track
  write_byte(ss, hd_sectors, inb_cmos(iobase+8));
}

#endif //else BX_USE_ATADRV


//////////////////////
// FLOPPY functions //
//////////////////////

  bx_bool
floppy_media_known(drive)
  Bit16u drive;
{
  Bit8u  val8;
  Bit16u media_state_offset;

  val8 = read_byte(0x0040, 0x003e); // diskette recal status
  if (drive)
    val8 >>= 1;
  val8 &= 0x01;
  if (val8 == 0)
    return(0);

  media_state_offset = 0x0090;
  if (drive)
    media_state_offset += 1;

  val8 = read_byte(0x0040, media_state_offset);
  val8 = (val8 >> 4) & 0x01;
  if (val8 == 0)
    return(0);

  // check pass, return KNOWN
  return(1);
}

  bx_bool
floppy_media_sense(drive)
  Bit16u drive;
{
  bx_bool retval;
  Bit16u  media_state_offset;
  Bit8u   drive_type, config_data, media_state;

  if (floppy_drive_recal(drive) == 0) {
    return(0);
    }

  // for now cheat and get drive type from CMOS,
  // assume media is same as drive type

  // ** config_data **
  // Bitfields for diskette media control:
  // Bit(s)  Description (Table M0028)
  //  7-6  last data rate set by controller
  //        00=500kbps, 01=300kbps, 10=250kbps, 11=1Mbps
  //  5-4  last diskette drive step rate selected
  //        00=0Ch, 01=0Dh, 10=0Eh, 11=0Ah
  //  3-2  {data rate at start of operation}
  //  1-0  reserved

  // ** media_state **
  // Bitfields for diskette drive media state:
  // Bit(s)  Description (Table M0030)
  //  7-6  data rate
  //    00=500kbps, 01=300kbps, 10=250kbps, 11=1Mbps
  //  5  double stepping required (e.g. 360kB in 1.2MB)
  //  4  media type established
  //  3  drive capable of supporting 4MB media
  //  2-0  on exit from BIOS, contains
  //    000 trying 360kB in 360kB
  //    001 trying 360kB in 1.2MB
  //    010 trying 1.2MB in 1.2MB
  //    011 360kB in 360kB established
  //    100 360kB in 1.2MB established
  //    101 1.2MB in 1.2MB established
  //    110 reserved
  //    111 all other formats/drives

  drive_type = inb_cmos(0x10);
  if (drive == 0)
    drive_type >>= 4;
  else
    drive_type &= 0x0f;
  if ( drive_type == 1 ) {
    // 360K 5.25" drive
    config_data = 0x00; // 0000 0000
    media_state = 0x25; // 0010 0101
    retval = 1;
    }
  else if ( drive_type == 2 ) {
    // 1.2 MB 5.25" drive
    config_data = 0x00; // 0000 0000
    media_state = 0x25; // 0010 0101   // need double stepping??? (bit 5)
    retval = 1;
    }
  else if ( drive_type == 3 ) {
    // 720K 3.5" drive
    config_data = 0x00; // 0000 0000 ???
    media_state = 0x17; // 0001 0111
    retval = 1;
    }
  else if ( drive_type == 4 ) {
    // 1.44 MB 3.5" drive
    config_data = 0x00; // 0000 0000
    media_state = 0x17; // 0001 0111
    retval = 1;
    }
  else if ( drive_type == 5 ) {
    // 2.88 MB 3.5" drive
    config_data = 0xCC; // 1100 1100
    media_state = 0xD7; // 1101 0111
    retval = 1;
    }
  //
  // Extended floppy size uses special cmos setting 
  else if ( drive_type == 6 ) {
    // 160k 5.25" drive
    config_data = 0x00; // 0000 0000
    media_state = 0x27; // 0010 0111
    retval = 1;
    }
  else if ( drive_type == 7 ) {
    // 180k 5.25" drive
    config_data = 0x00; // 0000 0000
    media_state = 0x27; // 0010 0111
    retval = 1;
    }
  else if ( drive_type == 8 ) {
    // 320k 5.25" drive
    config_data = 0x00; // 0000 0000
    media_state = 0x27; // 0010 0111
    retval = 1;
    }

  else {
    // not recognized
    config_data = 0x00; // 0000 0000
    media_state = 0x00; // 0000 0000
    retval = 0;
    }

  if (drive == 0)
    media_state_offset = 0x90;
  else
    media_state_offset = 0x91;
  write_byte(0x0040, 0x008B, config_data);
  write_byte(0x0040, media_state_offset, media_state);

  return(retval);
}

  bx_bool
floppy_drive_recal(drive)
  Bit16u drive;
{
  Bit8u  val8, dor;
  Bit16u curr_cyl_offset;

  // set 40:3e bit 7 to 0
  val8 = read_byte(0x0000, 0x043e);
  val8 &= 0x7f;
  write_byte(0x0000, 0x043e, val8);

  // turn on motor of selected drive, DMA & int enabled, normal operation
  if (drive)
    dor = 0x20;
  else
    dor = 0x10;
  dor |= 0x0c;
  dor |= drive;
  outb(0x03f2, dor);

  // reset the disk motor timeout value of INT 08
  write_byte(0x40,0x40, BX_FLOPPY_ON_CNT);

  // check port 3f4 for drive readiness
  val8 = inb(0x3f4);
  if ( (val8 & 0xf0) != 0x80 )
    BX_PANIC("floppy recal:f07: ctrl not ready\n");

  // send Recalibrate command (2 bytes) to controller
  outb(0x03f5, 0x07);  // 07: Recalibrate
  outb(0x03f5, drive); // 0=drive0, 1=drive1

 // turn on interrupts
ASM_START
  sti
ASM_END

  // wait on 40:3e bit 7 to become 1
  val8 = (read_byte(0x0000, 0x043e) & 0x80);
  while ( val8 == 0 ) {
    val8 = (read_byte(0x0000, 0x043e) & 0x80);
    }

 val8 = 0; // separate asm from while() loop
 // turn off interrupts
ASM_START
  cli
ASM_END

  // set 40:3e bit 7 to 0, and calibrated bit
  val8 = read_byte(0x0000, 0x043e);
  val8 &= 0x7f;
  if (drive) {
    val8 |= 0x02; // Drive 1 calibrated
    curr_cyl_offset = 0x0095;
    }
  else {
    val8 |= 0x01; // Drive 0 calibrated
    curr_cyl_offset = 0x0094;
    }
  write_byte(0x0040, 0x003e, val8);
  write_byte(0x0040, curr_cyl_offset, 0); // current cylinder is 0

  return(1);
}



  bx_bool
floppy_drive_exists(drive)
  Bit16u drive;
{
  Bit8u  drive_type;

  // check CMOS to see if drive exists
  drive_type = inb_cmos(0x10);
  if (drive == 0)
    drive_type >>= 4;
  else
    drive_type &= 0x0f;
  if ( drive_type == 0 )
    return(0);
  else
    return(1);
}

#if BX_SUPPORT_FLOPPY
  void
int13_diskette_function(DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS)
  Bit16u DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS;
{
  Bit8u  drive, num_sectors, track, sector, head, status;
  Bit16u base_address, base_count, base_es;
  Bit8u  page, mode_register, val8, dor;
  Bit8u  return_status[7];
  Bit8u  drive_type, num_floppies, ah;
  Bit16u es, last_addr;

  BX_DEBUG_INT13_FL("int13_diskette: AX=%04x BX=%04x CX=%04x DX=%04x ES=%04x\n", AX, BX, CX, DX, ES);
  // BX_DEBUG_INT13_FL("int13_diskette: SS=%04x DS=%04x ES=%04x DI=%04x SI=%04x\n",get_SS(), get_DS(), ES, DI, SI);

  ah = GET_AH();

  switch ( ah ) {
    case 0x00: // diskette controller reset
BX_DEBUG_INT13_FL("floppy f00\n");
      drive = GET_ELDL();
      if (drive > 1) {
        SET_AH(1); // invalid param
        set_diskette_ret_status(1);
        SET_CF();
        return;
        }
      drive_type = inb_cmos(0x10);

      if (drive == 0)
        drive_type >>= 4;
      else
        drive_type &= 0x0f;
      if (drive_type == 0) {
        SET_AH(0x80); // drive not responding
        set_diskette_ret_status(0x80);
        SET_CF();
        return;
        }
      SET_AH(0);
      set_diskette_ret_status(0);
      CLEAR_CF(); // successful
      set_diskette_current_cyl(drive, 0); // current cylinder
      return;

    case 0x01: // Read Diskette Status
      CLEAR_CF();
      val8 = read_byte(0x0000, 0x0441);
      SET_AH(val8);
      if (val8) {
        SET_CF();
        }
      return;

    case 0x02: // Read Diskette Sectors
    case 0x03: // Write Diskette Sectors
    case 0x04: // Verify Diskette Sectors
      num_sectors = GET_AL();
      track       = GET_CH();
      sector      = GET_CL();
      head        = GET_DH();
      drive       = GET_ELDL();

      if ( (drive > 1) || (head > 1) ||
           (num_sectors == 0) || (num_sectors > 72) ) {
BX_INFO("floppy: drive>1 || head>1 ...\n");
        SET_AH(1);
        set_diskette_ret_status(1);
        SET_AL(0); // no sectors read
        SET_CF(); // error occurred
        return;
        }

      // see if drive exists
      if (floppy_drive_exists(drive) == 0) {
        SET_AH(0x80); // not responding
        set_diskette_ret_status(0x80);
        SET_AL(0); // no sectors read
        SET_CF(); // error occurred
        return;
        }

      // see if media in drive, and type is known
      if (floppy_media_known(drive) == 0) {
        if (floppy_media_sense(drive) == 0) {
          SET_AH(0x0C); // Media type not found
          set_diskette_ret_status(0x0C);
          SET_AL(0); // no sectors read
          SET_CF(); // error occurred
          return;
          }
        }

      if (ah == 0x02) {
        // Read Diskette Sectors

        //-----------------------------------
        // set up DMA controller for transfer
        //-----------------------------------

        // es:bx = pointer to where to place information from diskette
        // port 04: DMA-1 base and current address, channel 2
        // port 05: DMA-1 base and current count, channel 2
        page = (ES >> 12);   // upper 4 bits
        base_es = (ES << 4); // lower 16bits contributed by ES
        base_address = base_es + BX; // lower 16 bits of address
                                     // contributed by ES:BX
        if ( base_address < base_es ) {
          // in case of carry, adjust page by 1
          page++;
          }
        base_count = (num_sectors * 512) - 1;

        // check for 64K boundary overrun
        last_addr = base_address + base_count;
        if (last_addr < base_address) {
          SET_AH(0x09);
          set_diskette_ret_status(0x09);
          SET_AL(0); // no sectors read
          SET_CF(); // error occurred
          return;
          }

        BX_DEBUG_INT13_FL("masking DMA-1 c2\n");
        outb(0x000a, 0x06);

  BX_DEBUG_INT13_FL("clear flip-flop\n");
        outb(0x000c, 0x00); // clear flip-flop
        outb(0x0004, base_address);
        outb(0x0004, base_address>>8);
  BX_DEBUG_INT13_FL("clear flip-flop\n");
        outb(0x000c, 0x00); // clear flip-flop
        outb(0x0005, base_count);
        outb(0x0005, base_count>>8);

        // port 0b: DMA-1 Mode Register
        mode_register = 0x46; // single mode, increment, autoinit disable,
                              // transfer type=write, channel 2
  BX_DEBUG_INT13_FL("setting mode register\n");
        outb(0x000b, mode_register);

  BX_DEBUG_INT13_FL("setting page register\n");
        // port 81: DMA-1 Page Register, channel 2
        outb(0x0081, page);

  BX_DEBUG_INT13_FL("unmask chan 2\n");
        outb(0x000a, 0x02); // unmask channel 2

        BX_DEBUG_INT13_FL("unmasking DMA-1 c2\n");
        outb(0x000a, 0x02);

        //--------------------------------------
        // set up floppy controller for transfer
        //--------------------------------------

        // set 40:3e bit 7 to 0
        val8 = read_byte(0x0000, 0x043e);
        val8 &= 0x7f;
        write_byte(0x0000, 0x043e, val8);

        // turn on motor of selected drive, DMA & int enabled, normal operation
        if (drive)
          dor = 0x20;
        else
          dor = 0x10;
        dor |= 0x0c;
        dor |= drive;
        outb(0x03f2, dor);

        // reset the disk motor timeout value of INT 08
        write_byte(0x40,0x40, BX_FLOPPY_ON_CNT);

        // check port 3f4 for drive readiness
        val8 = inb(0x3f4);
        if ( (val8 & 0xf0) != 0x80 )
          BX_PANIC("int13_diskette:f02: ctrl not ready\n");

        // send read-normal-data command (9 bytes) to controller
        outb(0x03f5, 0xe6); // e6: read normal data
        outb(0x03f5, (head << 2) | drive); // HD DR1 DR2
        outb(0x03f5, track);
        outb(0x03f5, head);
        outb(0x03f5, sector);
        outb(0x03f5, 2); // 512 byte sector size
        outb(0x03f5, 0); // last sector number possible on track
        outb(0x03f5, 0); // Gap length
        outb(0x03f5, 0xff); // Gap length

       // turn on interrupts
  ASM_START
        sti
  ASM_END

        // wait on 40:3e bit 7 to become 1
        val8 = (read_byte(0x0000, 0x043e) & 0x80);
        while ( val8 == 0 ) {
          val8 = (read_byte(0x0000, 0x043e) & 0x80);
          }

       val8 = 0; // separate asm from while() loop
       // turn off interrupts
  ASM_START
        cli
  ASM_END

        // set 40:3e bit 7 to 0
        val8 = read_byte(0x0000, 0x043e);
        val8 &= 0x7f;
        write_byte(0x0000, 0x043e, val8);

        // check port 3f4 for accessibility to status bytes
        val8 = inb(0x3f4);
        if ( (val8 & 0xc0) != 0xc0 )
          BX_PANIC("int13_diskette: ctrl not ready\n");

        // read 7 return status bytes from controller
        // using loop index broken, have to unroll...
        return_status[0] = inb(0x3f5);
        return_status[1] = inb(0x3f5);
        return_status[2] = inb(0x3f5);
        return_status[3] = inb(0x3f5);
        return_status[4] = inb(0x3f5);
        return_status[5] = inb(0x3f5);
        return_status[6] = inb(0x3f5);
        // record in BIOS Data Area
        write_byte(0x0040, 0x0042, return_status[0]);
        write_byte(0x0040, 0x0043, return_status[1]);
        write_byte(0x0040, 0x0044, return_status[2]);
        write_byte(0x0040, 0x0045, return_status[3]);
        write_byte(0x0040, 0x0046, return_status[4]);
        write_byte(0x0040, 0x0047, return_status[5]);
        write_byte(0x0040, 0x0048, return_status[6]);

        if ( (return_status[0] & 0xc0) != 0 ) {
          SET_AH(0x20);
          set_diskette_ret_status(0x20);
          SET_AL(0); // no sectors read
          SET_CF(); // error occurred
          return;
          }

        // ??? should track be new val from return_status[3] ?
        set_diskette_current_cyl(drive, track);
        // AL = number of sectors read (same value as passed)
        SET_AH(0x00); // success
        CLEAR_CF();   // success
        return;
        }
      else if (ah == 0x03) {
        // Write Diskette Sectors

        //-----------------------------------
        // set up DMA controller for transfer
        //-----------------------------------

        // es:bx = pointer to where to place information from diskette
        // port 04: DMA-1 base and current address, channel 2
        // port 05: DMA-1 base and current count, channel 2
        page = (ES >> 12);   // upper 4 bits
        base_es = (ES << 4); // lower 16bits contributed by ES
        base_address = base_es + BX; // lower 16 bits of address
                                     // contributed by ES:BX
        if ( base_address < base_es ) {
          // in case of carry, adjust page by 1
          page++;
          }
        base_count = (num_sectors * 512) - 1;

        // check for 64K boundary overrun
        last_addr = base_address + base_count;
        if (last_addr < base_address) {
          SET_AH(0x09);
          set_diskette_ret_status(0x09);
          SET_AL(0); // no sectors read
          SET_CF(); // error occurred
          return;
          }

        BX_DEBUG_INT13_FL("masking DMA-1 c2\n");
        outb(0x000a, 0x06);

        outb(0x000c, 0x00); // clear flip-flop
        outb(0x0004, base_address);
        outb(0x0004, base_address>>8);
        outb(0x000c, 0x00); // clear flip-flop
        outb(0x0005, base_count);
        outb(0x0005, base_count>>8);

        // port 0b: DMA-1 Mode Register
        mode_register = 0x4a; // single mode, increment, autoinit disable,
                              // transfer type=read, channel 2
        outb(0x000b, mode_register);

        // port 81: DMA-1 Page Register, channel 2
        outb(0x0081, page);

        BX_DEBUG_INT13_FL("unmasking DMA-1 c2\n");
        outb(0x000a, 0x02);

        //--------------------------------------
        // set up floppy controller for transfer
        //--------------------------------------

        // set 40:3e bit 7 to 0
        val8 = read_byte(0x0000, 0x043e);
        val8 &= 0x7f;
        write_byte(0x0000, 0x043e, val8);

        // turn on motor of selected drive, DMA & int enabled, normal operation
        if (drive)
          dor = 0x20;
        else
          dor = 0x10;
        dor |= 0x0c;
        dor |= drive;
        outb(0x03f2, dor);

        // reset the disk motor timeout value of INT 08
        write_byte(0x40,0x40, BX_FLOPPY_ON_CNT);

        // check port 3f4 for drive readiness
        val8 = inb(0x3f4);
        if ( (val8 & 0xf0) != 0x80 )
          BX_PANIC("int13_diskette:f03: ctrl not ready\n");

        // send read-normal-data command (9 bytes) to controller
        outb(0x03f5, 0xc5); // c5: write normal data
        outb(0x03f5, (head << 2) | drive); // HD DR1 DR2
        outb(0x03f5, track);
        outb(0x03f5, head);
        outb(0x03f5, sector);
        outb(0x03f5, 2); // 512 byte sector size
        outb(0x03f5, 0); // last sector number possible on track
        outb(0x03f5, 0); // Gap length
        outb(0x03f5, 0xff); // Gap length

       // turn on interrupts
  ASM_START
        sti
  ASM_END

        // wait on 40:3e bit 7 to become 1
        val8 = (read_byte(0x0000, 0x043e) & 0x80);
        while ( val8 == 0 ) {
          val8 = (read_byte(0x0000, 0x043e) & 0x80);
          }

       val8 = 0; // separate asm from while() loop
       // turn off interrupts
  ASM_START
        cli
  ASM_END

        // set 40:3e bit 7 to 0
        val8 = read_byte(0x0000, 0x043e);
        val8 &= 0x7f;
        write_byte(0x0000, 0x043e, val8);

        // check port 3f4 for accessibility to status bytes
        val8 = inb(0x3f4);
        if ( (val8 & 0xc0) != 0xc0 )
          BX_PANIC("int13_diskette: ctrl not ready\n");

        // read 7 return status bytes from controller
        // using loop index broken, have to unroll...
        return_status[0] = inb(0x3f5);
        return_status[1] = inb(0x3f5);
        return_status[2] = inb(0x3f5);
        return_status[3] = inb(0x3f5);
        return_status[4] = inb(0x3f5);
        return_status[5] = inb(0x3f5);
        return_status[6] = inb(0x3f5);
        // record in BIOS Data Area
        write_byte(0x0040, 0x0042, return_status[0]);
        write_byte(0x0040, 0x0043, return_status[1]);
        write_byte(0x0040, 0x0044, return_status[2]);
        write_byte(0x0040, 0x0045, return_status[3]);
        write_byte(0x0040, 0x0046, return_status[4]);
        write_byte(0x0040, 0x0047, return_status[5]);
        write_byte(0x0040, 0x0048, return_status[6]);

        if ( (return_status[0] & 0xc0) != 0 ) {
          if ( (return_status[1] & 0x02) != 0 ) {
            // diskette not writable.
            // AH=status code=0x03 (tried to write on write-protected disk)
            // AL=number of sectors written=0
            AX = 0x0300;
            SET_CF();
            return;
          } else {
            BX_PANIC("int13_diskette_function: read error\n");
          }
        }

        // ??? should track be new val from return_status[3] ?
        set_diskette_current_cyl(drive, track);
        // AL = number of sectors read (same value as passed)
        SET_AH(0x00); // success
        CLEAR_CF();   // success
        return;
        }
      else {  // if (ah == 0x04)
        // Verify Diskette Sectors

        // ??? should track be new val from return_status[3] ?
        set_diskette_current_cyl(drive, track);
        // AL = number of sectors verified (same value as passed)
        CLEAR_CF();   // success
        SET_AH(0x00); // success
        return;
        }


    case 0x05: // format diskette track
BX_DEBUG_INT13_FL("floppy f05\n");

      num_sectors = GET_AL();
      track       = GET_CH();
      head        = GET_DH();
      drive       = GET_ELDL();

      if ((drive > 1) || (head > 1) || (track > 79) ||
          (num_sectors == 0) || (num_sectors > 18)) {
        SET_AH(1);
        set_diskette_ret_status(1);
        SET_CF(); // error occurred
        }

      // see if drive exists
      if (floppy_drive_exists(drive) == 0) {
        SET_AH(0x80); // drive not responding
        set_diskette_ret_status(0x80);
        SET_CF(); // error occurred
        return;
        }

      // see if media in drive, and type is known
      if (floppy_media_known(drive) == 0) {
        if (floppy_media_sense(drive) == 0) {
          SET_AH(0x0C); // Media type not found
          set_diskette_ret_status(0x0C);
          SET_AL(0); // no sectors read
          SET_CF(); // error occurred
          return;
          }
        }

      // set up DMA controller for transfer
      page = (ES >> 12);   // upper 4 bits
      base_es = (ES << 4); // lower 16bits contributed by ES
      base_address = base_es + BX; // lower 16 bits of address
                                   // contributed by ES:BX
      if ( base_address < base_es ) {
        // in case of carry, adjust page by 1
        page++;
        }
      base_count = (num_sectors * 4) - 1;

      // check for 64K boundary overrun
      last_addr = base_address + base_count;
      if (last_addr < base_address) {
        SET_AH(0x09);
        set_diskette_ret_status(0x09);
        SET_AL(0); // no sectors read
        SET_CF(); // error occurred
        return;
        }

      outb(0x000a, 0x06);
      outb(0x000c, 0x00); // clear flip-flop
      outb(0x0004, base_address);
      outb(0x0004, base_address>>8);
      outb(0x000c, 0x00); // clear flip-flop
      outb(0x0005, base_count);
      outb(0x0005, base_count>>8);
      mode_register = 0x4a; // single mode, increment, autoinit disable,
                            // transfer type=read, channel 2
      outb(0x000b, mode_register);
      // port 81: DMA-1 Page Register, channel 2
      outb(0x0081, page);
      outb(0x000a, 0x02);

      // set up floppy controller for transfer
      val8 = read_byte(0x0000, 0x043e);
      val8 &= 0x7f;
      write_byte(0x0000, 0x043e, val8);
      // turn on motor of selected drive, DMA & int enabled, normal operation
      if (drive)
        dor = 0x20;
      else
        dor = 0x10;
      dor |= 0x0c;
      dor |= drive;
      outb(0x03f2, dor);

      // reset the disk motor timeout value of INT 08
      write_byte(0x40,0x40, BX_FLOPPY_ON_CNT);

      // check port 3f4 for drive readiness
      val8 = inb(0x3f4);
      if ( (val8 & 0xf0) != 0x80 )
        BX_PANIC("int13_diskette:f05: ctrl not ready\n");

      // send read-normal-data command (6 bytes) to controller
      outb(0x03f5, 0x4d); // 4d: format track
      outb(0x03f5, (head << 2) | drive); // HD DR1 DR2
      outb(0x03f5, 2); // 512 byte sector size
      outb(0x03f5, num_sectors); // number of sectors per track
      outb(0x03f5, 0); // Gap length
      outb(0x03f5, 0xf6); // Fill byte
      // turn on interrupts
  ASM_START
      sti
  ASM_END
      // wait on 40:3e bit 7 to become 1
      val8 = (read_byte(0x0000, 0x043e) & 0x80);
      while ( val8 == 0 ) {
        val8 = (read_byte(0x0000, 0x043e) & 0x80);
        }
     val8 = 0; // separate asm from while() loop
     // turn off interrupts
  ASM_START
      cli
  ASM_END
      // set 40:3e bit 7 to 0
      val8 = read_byte(0x0000, 0x043e);
      val8 &= 0x7f;
      write_byte(0x0000, 0x043e, val8);
      // check port 3f4 for accessibility to status bytes
      val8 = inb(0x3f4);
      if ( (val8 & 0xc0) != 0xc0 )
        BX_PANIC("int13_diskette: ctrl not ready\n");

      // read 7 return status bytes from controller
      // using loop index broken, have to unroll...
      return_status[0] = inb(0x3f5);
      return_status[1] = inb(0x3f5);
      return_status[2] = inb(0x3f5);
      return_status[3] = inb(0x3f5);
      return_status[4] = inb(0x3f5);
      return_status[5] = inb(0x3f5);
      return_status[6] = inb(0x3f5);
      // record in BIOS Data Area
      write_byte(0x0040, 0x0042, return_status[0]);
      write_byte(0x0040, 0x0043, return_status[1]);
      write_byte(0x0040, 0x0044, return_status[2]);
      write_byte(0x0040, 0x0045, return_status[3]);
      write_byte(0x0040, 0x0046, return_status[4]);
      write_byte(0x0040, 0x0047, return_status[5]);
      write_byte(0x0040, 0x0048, return_status[6]);

      if ( (return_status[0] & 0xc0) != 0 ) {
        if ( (return_status[1] & 0x02) != 0 ) {
          // diskette not writable.
          // AH=status code=0x03 (tried to write on write-protected disk)
          // AL=number of sectors written=0
          AX = 0x0300;
          SET_CF();
          return;
        } else {
          BX_PANIC("int13_diskette_function: write error\n");
        }
      }

      SET_AH(0);
      set_diskette_ret_status(0);
      set_diskette_current_cyl(drive, 0);
      CLEAR_CF(); // successful
      return;


    case 0x08: // read diskette drive parameters
BX_DEBUG_INT13_FL("floppy f08\n");
      drive = GET_ELDL();

      if (drive > 1) {
        AX = 0;
        BX = 0;
        CX = 0;
        DX = 0;
        ES = 0;
        DI = 0;
        SET_DL(num_floppies);
        SET_CF();
        return;
        }

      drive_type = inb_cmos(0x10);
      num_floppies = 0;
      if (drive_type & 0xf0)
        num_floppies++;
      if (drive_type & 0x0f)
        num_floppies++;

      if (drive == 0)
        drive_type >>= 4;
      else
        drive_type &= 0x0f;

      SET_BH(0);
      SET_BL(drive_type);
      SET_AH(0);
      SET_AL(0);
      SET_DL(num_floppies);

      switch (drive_type) {
        case 0: // none
          CX = 0;
          SET_DH(0); // max head #
          break;

        case 1: // 360KB, 5.25"
          CX = 0x2709; // 40 tracks, 9 sectors
          SET_DH(1); // max head #
          break;

        case 2: // 1.2MB, 5.25"
          CX = 0x4f0f; // 80 tracks, 15 sectors
          SET_DH(1); // max head #
          break;

        case 3: // 720KB, 3.5"
          CX = 0x4f09; // 80 tracks, 9 sectors
          SET_DH(1); // max head #
          break;

        case 4: // 1.44MB, 3.5"
          CX = 0x4f12; // 80 tracks, 18 sectors
          SET_DH(1); // max head #
          break;

        case 5: // 2.88MB, 3.5"
          CX = 0x4f24; // 80 tracks, 36 sectors
          SET_DH(1); // max head #
          break;

        case 6: // 160k, 5.25"
          CX = 0x2708; // 40 tracks, 8 sectors
          SET_DH(0); // max head #
          break;

        case 7: // 180k, 5.25"
          CX = 0x2709; // 40 tracks, 9 sectors
          SET_DH(0); // max head #
          break;

        case 8: // 320k, 5.25"
          CX = 0x2708; // 40 tracks, 8 sectors
          SET_DH(1); // max head #
          break;

        default: // ?
          BX_PANIC("floppy: int13: bad floppy type\n");
        }

      /* set es & di to point to 11 byte diskette param table in ROM */
ASM_START
      push bp
      mov  bp, sp
      mov ax, #diskette_param_table2
      mov _int13_diskette_function.DI+2[bp], ax
      mov _int13_diskette_function.ES+2[bp], cs
      pop  bp
ASM_END
      CLEAR_CF(); // success
      /* disk status not changed upon success */
      return;


    case 0x15: // read diskette drive type
BX_DEBUG_INT13_FL("floppy f15\n");
      drive = GET_ELDL();
      if (drive > 1) {
        SET_AH(0); // only 2 drives supported
        // set_diskette_ret_status here ???
        SET_CF();
        return;
        }
      drive_type = inb_cmos(0x10);

      if (drive == 0)
        drive_type >>= 4;
      else
        drive_type &= 0x0f;
      CLEAR_CF(); // successful, not present
      if (drive_type==0) {
        SET_AH(0); // drive not present
        }
      else {
        SET_AH(1); // drive present, does not support change line
        }

      return;

    case 0x16: // get diskette change line status
BX_DEBUG_INT13_FL("floppy f16\n");
      drive = GET_ELDL();
      if (drive > 1) {
        SET_AH(0x01); // invalid drive
        set_diskette_ret_status(0x01);
        SET_CF();
        return;
        }

      SET_AH(0x06); // change line not supported
      set_diskette_ret_status(0x06);
      SET_CF();
      return;

    case 0x17: // set diskette type for format(old)
BX_DEBUG_INT13_FL("floppy f17\n");
      /* not used for 1.44M floppies */
      SET_AH(0x01); // not supported
      set_diskette_ret_status(1); /* not supported */
      SET_CF();
      return;

    case 0x18: // set diskette type for format(new)
BX_DEBUG_INT13_FL("floppy f18\n");
      SET_AH(0x01); // do later
      set_diskette_ret_status(1);
      SET_CF();
      return;

    default:
        BX_INFO("int13_diskette: unsupported AH=%02x\n", GET_AH());

      // if ( (ah==0x20) || ((ah>=0x41) && (ah<=0x49)) || (ah==0x4e) ) {
        SET_AH(0x01); // ???
        set_diskette_ret_status(1);
        SET_CF();
        return;
      //   }
    }
}
#else  // #if BX_SUPPORT_FLOPPY
  void
int13_diskette_function(DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS)
  Bit16u DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS;
{
  Bit8u  val8;

  switch ( GET_AH() ) {

    case 0x01: // Read Diskette Status
      CLEAR_CF();
      val8 = read_byte(0x0000, 0x0441);
      SET_AH(val8);
      if (val8) {
        SET_CF();
        }
      return;

    default:
      SET_CF();
      write_byte(0x0000, 0x0441, 0x01);
      SET_AH(0x01);
    }
}
#endif  // #if BX_SUPPORT_FLOPPY

 void
set_diskette_ret_status(value)
  Bit8u value;
{
  write_byte(0x0040, 0x0041, value);
}

  void
set_diskette_current_cyl(drive, cyl)
  Bit8u drive;
  Bit8u cyl;
{
  if (drive > 1)
    BX_PANIC("set_diskette_current_cyl(): drive > 1\n");
  write_byte(0x0040, 0x0094+drive, cyl);
}

  void
determine_floppy_media(drive)
  Bit16u drive;
{
#if 0
  Bit8u  val8, DOR, ctrl_info;

  ctrl_info = read_byte(0x0040, 0x008F);
  if (drive==1)
    ctrl_info >>= 4;
  else
    ctrl_info &= 0x0f;

#if 0
  if (drive == 0) {
    DOR = 0x1c; // DOR: drive0 motor on, DMA&int enabled, normal op, drive select 0
    }
  else {
    DOR = 0x2d; // DOR: drive1 motor on, DMA&int enabled, normal op, drive select 1
    }
#endif

  if ( (ctrl_info & 0x04) != 0x04 ) {
    // Drive not determined means no drive exists, done.
    return;
    }

#if 0
  // check Main Status Register for readiness
  val8 = inb(0x03f4) & 0x80; // Main Status Register
  if (val8 != 0x80)
    BX_PANIC("d_f_m: MRQ bit not set\n");

  // change line

  // existing BDA values

  // turn on drive motor
  outb(0x03f2, DOR); // Digital Output Register
  //
#endif
  BX_PANIC("d_f_m: OK so far\n");
#endif
}

  void
int17_function(regs, ds, iret_addr)
  pusha_regs_t regs; // regs pushed from PUSHA instruction
  Bit16u ds; // previous DS:, DS set to 0x0000 by asm wrapper
  iret_addr_t  iret_addr; // CS,IP,Flags pushed from original INT call
{
  Bit16u addr,timeout;
  Bit8u val8;

  ASM_START
  sti
  ASM_END

  addr = read_word(0x0040, (regs.u.r16.dx << 1) + 8);
  if ((regs.u.r8.ah < 3) && (regs.u.r16.dx < 3) && (addr > 0)) {
    timeout = read_byte(0x0040, 0x0078 + regs.u.r16.dx) << 8;
    if (regs.u.r8.ah == 0) {
      outb(addr, regs.u.r8.al);
      val8 = inb(addr+2);
      outb(addr+2, val8 | 0x01); // send strobe
      ASM_START
      nop
      ASM_END
      outb(addr+2, val8 & ~0x01);
      while (((inb(addr+1) & 0x40) == 0x40) && (timeout)) {
        timeout--;
      }
    }
    if (regs.u.r8.ah == 1) {
      val8 = inb(addr+2);
      outb(addr+2, val8 & ~0x04); // send init
      ASM_START
      nop
      ASM_END
      outb(addr+2, val8 | 0x04);
    }
    val8 = inb(addr+1);
    regs.u.r8.ah = (val8 ^ 0x48);
    if (!timeout) regs.u.r8.ah |= 0x01;
    ClearCF(iret_addr.flags);
  } else {
    SetCF(iret_addr.flags); // Unsupported
  }
}

void
int18_function(seq_nr)
Bit16u seq_nr;
{
  Bit16u ebda_seg=read_word(0x0040,0x000E);
  Bit16u bootdev;
  Bit8u  bootdrv;
  Bit8u  bootchk;
  Bit16u bootseg;
  Bit16u bootip;
  Bit16u status;

  struct ipl_entry e;

  // if BX_ELTORITO_BOOT is not defined, old behavior
  //   check bit 5 in CMOS reg 0x2d.  load either 0x00 or 0x80 into DL
  //   in preparation for the intial INT 13h (0=floppy A:, 0x80=C:)
  //     0: system boot sequence, first drive C: then A:
  //     1: system boot sequence, first drive A: then C:
  // else BX_ELTORITO_BOOT is defined
  //   CMOS regs 0x3D and 0x38 contain the boot sequence:
  //     CMOS reg 0x3D & 0x0f : 1st boot device
  //     CMOS reg 0x3D & 0xf0 : 2nd boot device
  //     CMOS reg 0x38 & 0xf0 : 3rd boot device
  //   boot device codes:
  //     0x00 : not defined
  //     0x01 : first floppy 
  //     0x02 : first harddrive
  //     0x03 : first cdrom
  //     0x04 - 0x0f : PnP expansion ROMs (e.g. Etherboot)
  //     else : boot failure

  // Get the boot sequence
#if BX_ELTORITO_BOOT
  bootdev = inb_cmos(0x3d);
  bootdev |= ((inb_cmos(0x38) & 0xf0) << 4);
  bootdev >>= 4 * seq_nr;
  bootdev &= 0xf;
  if (bootdev == 0) BX_PANIC("No bootable device.\n");
  
  /* Translate from CMOS runes to an IPL table offset by subtracting 1 */
  bootdev -= 1;
#else  
  if (seq_nr ==2) BX_PANIC("No more boot devices.");
  if (!!(inb_cmos(0x2d) & 0x20) ^ (seq_nr == 1)) 
      /* Boot from floppy if the bit is set or it's the second boot */
    bootdev = 0x00;
  else 
    bootdev = 0x01;
#endif

  /* Read the boot device from the IPL table */
  if (get_boot_vector(bootdev, &e) == 0) {
    BX_INFO("Invalid boot device (0x%x)\n", bootdev);
    return;
  }

  /* Do the loading, and set up vector as a far pointer to the boot
   * address, and bootdrv as the boot drive */
  print_boot_device(e.type);

  switch(e.type) {
  case 0x01: /* FDD */
  case 0x02: /* HDD */

    bootdrv = (e.type == 0x02) ? 0x80 : 0x00;
    bootseg = 0x07c0;
    status = 0;

ASM_START
    push bp
    mov  bp, sp
    push ax
    push bx
    push cx
    push dx

    mov  dl, _int18_function.bootdrv + 2[bp]
    mov  ax, _int18_function.bootseg + 2[bp]
    mov  es, ax         ;; segment
    mov  bx, #0x0000    ;; offset
    mov  ah, #0x02      ;; function 2, read diskette sector
    mov  al, #0x01      ;; read 1 sector
    mov  ch, #0x00      ;; track 0
    mov  cl, #0x01      ;; sector 1
    mov  dh, #0x00      ;; head 0
    int  #0x13          ;; read sector
    jnc  int19_load_done
    mov  ax, #0x0001
    mov  _int18_function.status + 2[bp], ax

int19_load_done:
    pop  dx
    pop  cx
    pop  bx
    pop  ax
    pop  bp
ASM_END
    
    if (status != 0) {
      print_boot_failure(e.type, 1);
      return;
    }

    /* Always check the signature on a HDD boot sector; on FDD, only do
     * the check if the CMOS doesn't tell us to skip it */
    if (e.type != 0x00 || !((inb_cmos(0x38) & 0x01))) {
      if (read_word(bootseg,0x1fe) != 0xaa55) {
        print_boot_failure(e.type, 0);
        return;
      }
    }

#if BX_TCGBIOS
    tcpa_add_bootdevice((Bit32u)0L, (Bit32u)bootdrv);
#endif

    /* Canonicalize bootseg:bootip */
    bootip = (bootseg & 0x0fff) << 4;
    bootseg &= 0xf000;
  break;

#if BX_ELTORITO_BOOT
  case 0x03: /* CD-ROM */
    status = cdrom_boot();

    // If failure
    if ( (status & 0x00ff) !=0 ) {
      print_cdromboot_failure(status);
      print_boot_failure(e.type, 1);
      return;
    }

    bootdrv = (Bit8u)(status>>8);
    bootseg = read_word(ebda_seg,&EbdaData->cdemu.load_segment);
#if BX_TCGBIOS
    tcpa_add_bootdevice((Bit32u)1L, (Bit32u)0L);
#endif

    /* Canonicalize bootseg:bootip */
    bootip = (bootseg & 0x0fff) << 4;
    bootseg &= 0xf000;
    break;
#endif

  case 0x80: /* Expansion ROM with a Bootstrap Entry Vector (a far pointer) */
    bootseg = e.vector >> 16;
    bootip = e.vector & 0xffff;
    break;

  default: return;
  }

#if BX_TCGBIOS
  tcpa_ipl((Bit32u)bootseg);               /* specs: 8.2.3 steps 4 and 5 */
#endif
  
  /* Jump to the boot vector */
ASM_START
    mov  bp, sp
    ;; Build an iret stack frame that will take us to the boot vector.
    ;; iret pops ip, then cs, then flags, so push them in the opposite order.
    pushf
    mov  ax, _int18_function.bootseg + 0[bp] 
    push ax
    mov  ax, _int18_function.bootip + 0[bp] 
    push ax
    ;; Set the magic number in ax and the boot drive in dl.
    mov  ax, #0xaa55
    mov  dl, _int18_function.bootdrv + 0[bp]
    ;; Zero some of the other registers.
    xor  bx, bx
    mov  ds, bx
    mov  es, bx
    mov  bp, bx
    ;; Go!
    iret
ASM_END
}

  void
int1a_function(regs, ds, iret_addr)
  pusha_regs_t regs; // regs pushed from PUSHA instruction
  Bit16u ds; // previous DS:, DS set to 0x0000 by asm wrapper
  iret_addr_t  iret_addr; // CS,IP,Flags pushed from original INT call
{
  Bit8u val8;

  BX_DEBUG_INT1A("int1a: AX=%04x BX=%04x CX=%04x DX=%04x DS=%04x\n", regs.u.r16.ax, regs.u.r16.bx, regs.u.r16.cx, regs.u.r16.dx, ds);

  ASM_START
  sti
  ASM_END

  switch (regs.u.r8.ah) {
    case 0: // get current clock count
      ASM_START
      cli
      ASM_END
      regs.u.r16.cx = BiosData->ticks_high;
      regs.u.r16.dx = BiosData->ticks_low;
      regs.u.r8.al  = BiosData->midnight_flag;
      BiosData->midnight_flag = 0; // reset flag
      ASM_START
      sti
      ASM_END
      // AH already 0
      ClearCF(iret_addr.flags); // OK
      break;

    case 1: // Set Current Clock Count
      ASM_START
      cli
      ASM_END
      BiosData->ticks_high = regs.u.r16.cx;
      BiosData->ticks_low  = regs.u.r16.dx;
      BiosData->midnight_flag = 0; // reset flag
      ASM_START
      sti
      ASM_END
      regs.u.r8.ah = 0;
      ClearCF(iret_addr.flags); // OK
      break;


    case 2: // Read CMOS Time
      if (rtc_updating()) {
        SetCF(iret_addr.flags);
        break;
        }

      regs.u.r8.dh = inb_cmos(0x00); // Seconds
      regs.u.r8.cl = inb_cmos(0x02); // Minutes
      regs.u.r8.ch = inb_cmos(0x04); // Hours
      regs.u.r8.dl = inb_cmos(0x0b) & 0x01; // Stat Reg B
      regs.u.r8.ah = 0;
      regs.u.r8.al = regs.u.r8.ch;
      ClearCF(iret_addr.flags); // OK
      break;

    case 3: // Set CMOS Time
      // Using a debugger, I notice the following masking/setting
      // of bits in Status Register B, by setting Reg B to
      // a few values and getting its value after INT 1A was called.
      //
      //        try#1       try#2       try#3
      // before 1111 1101   0111 1101   0000 0000
      // after  0110 0010   0110 0010   0000 0010
      //
      // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
      // My assumption: RegB = ((RegB & 01100000b) | 00000010b)
      if (rtc_updating()) {
        init_rtc();
        // fall through as if an update were not in progress
        }
      outb_cmos(0x00, regs.u.r8.dh); // Seconds
      outb_cmos(0x02, regs.u.r8.cl); // Minutes
      outb_cmos(0x04, regs.u.r8.ch); // Hours
      // Set Daylight Savings time enabled bit to requested value
      val8 = (inb_cmos(0x0b) & 0x60) | 0x02 | (regs.u.r8.dl & 0x01);
      // (reg B already selected)
      outb_cmos(0x0b, val8);
      regs.u.r8.ah = 0;
      regs.u.r8.al = val8; // val last written to Reg B
      ClearCF(iret_addr.flags); // OK
      break;

    case 4: // Read CMOS Date
      regs.u.r8.ah = 0;
      if (rtc_updating()) {
        SetCF(iret_addr.flags);
        break;
        }
      regs.u.r8.cl = inb_cmos(0x09); // Year
      regs.u.r8.dh = inb_cmos(0x08); // Month
      regs.u.r8.dl = inb_cmos(0x07); // Day of Month
      regs.u.r8.ch = inb_cmos(0x32); // Century
      regs.u.r8.al = regs.u.r8.ch;
      ClearCF(iret_addr.flags); // OK
      break;

    case 5: // Set CMOS Date
      // Using a debugger, I notice the following masking/setting
      // of bits in Status Register B, by setting Reg B to
      // a few values and getting its value after INT 1A was called.
      //
      //        try#1       try#2       try#3       try#4
      // before 1111 1101   0111 1101   0000 0010   0000 0000
      // after  0110 1101   0111 1101   0000 0010   0000 0000
      //
      // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
      // My assumption: RegB = (RegB & 01111111b)
      if (rtc_updating()) {
        init_rtc();
        SetCF(iret_addr.flags);
        break;
        }
      outb_cmos(0x09, regs.u.r8.cl); // Year
      outb_cmos(0x08, regs.u.r8.dh); // Month
      outb_cmos(0x07, regs.u.r8.dl); // Day of Month
      outb_cmos(0x32, regs.u.r8.ch); // Century
      val8 = inb_cmos(0x0b) & 0x7f; // clear halt-clock bit
      outb_cmos(0x0b, val8);
      regs.u.r8.ah = 0;
      regs.u.r8.al = val8; // AL = val last written to Reg B
      ClearCF(iret_addr.flags); // OK
      break;

    case 6: // Set Alarm Time in CMOS
      // Using a debugger, I notice the following masking/setting
      // of bits in Status Register B, by setting Reg B to
      // a few values and getting its value after INT 1A was called.
      //
      //        try#1       try#2       try#3
      // before 1101 1111   0101 1111   0000 0000
      // after  0110 1111   0111 1111   0010 0000
      //
      // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
      // My assumption: RegB = ((RegB & 01111111b) | 00100000b)
      val8 = inb_cmos(0x0b); // Get Status Reg B
      regs.u.r16.ax = 0;
      if (val8 & 0x20) {
        // Alarm interrupt enabled already
        SetCF(iret_addr.flags); // Error: alarm in use
        break;
        }
      if (rtc_updating()) {
        init_rtc();
        // fall through as if an update were not in progress
        }
      outb_cmos(0x01, regs.u.r8.dh); // Seconds alarm
      outb_cmos(0x03, regs.u.r8.cl); // Minutes alarm
      outb_cmos(0x05, regs.u.r8.ch); // Hours alarm
      outb(0xa1, inb(0xa1) & 0xfe); // enable IRQ 8
      // enable Status Reg B alarm bit, clear halt clock bit
      outb_cmos(0x0b, (val8 & 0x7f) | 0x20);
      ClearCF(iret_addr.flags); // OK
      break;

    case 7: // Turn off Alarm
      // Using a debugger, I notice the following masking/setting
      // of bits in Status Register B, by setting Reg B to
      // a few values and getting its value after INT 1A was called.
      //
      //        try#1       try#2       try#3       try#4
      // before 1111 1101   0111 1101   0010 0000   0010 0010
      // after  0100 0101   0101 0101   0000 0000   0000 0010
      //
      // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
      // My assumption: RegB = (RegB & 01010111b)
      val8 = inb_cmos(0x0b); // Get Status Reg B
      // clear clock-halt bit, disable alarm bit
      outb_cmos(0x0b, val8 & 0x57); // disable alarm bit
      regs.u.r8.ah = 0;
      regs.u.r8.al = val8; // val last written to Reg B
      ClearCF(iret_addr.flags); // OK
      break;
#if BX_PCIBIOS
    case 0xb1:
      // real mode PCI BIOS functions now handled in assembler code
      // this C code handles the error code for information only
      if (regs.u.r8.bl == 0xff) {
        BX_INFO("PCI BIOS: PCI not present\n");
      } else if (regs.u.r8.bl == 0x81) {
        BX_INFO("unsupported PCI BIOS function 0x%02x\n", regs.u.r8.al);
      } else if (regs.u.r8.bl == 0x83) {
        BX_INFO("bad PCI vendor ID %04x\n", regs.u.r16.dx);
      } else if (regs.u.r8.bl == 0x86) {
        BX_INFO("PCI device %04x:%04x not found\n", regs.u.r16.dx, regs.u.r16.cx);
      }
      regs.u.r8.ah = regs.u.r8.bl;
      SetCF(iret_addr.flags);
      break;
#endif

    default:
      SetCF(iret_addr.flags); // Unsupported
    }
}

  void
int70_function(regs, ds, iret_addr)
  pusha_regs_t regs; // regs pushed from PUSHA instruction
  Bit16u ds; // previous DS:, DS set to 0x0000 by asm wrapper
  iret_addr_t  iret_addr; // CS,IP,Flags pushed from original INT call
{
  // INT 70h: IRQ 8 - CMOS RTC interrupt from periodic or alarm modes
  Bit8u registerB = 0, registerC = 0;

  // Check which modes are enabled and have occurred.
  registerB = inb_cmos( 0xB );
  registerC = inb_cmos( 0xC );

  if( ( registerB & 0x60 ) != 0 ) {
    if( ( registerC & 0x20 ) != 0 ) {
      // Handle Alarm Interrupt.
ASM_START
      sti
      int #0x4a
      cli
ASM_END
    }
    if( ( registerC & 0x40 ) != 0 ) {
      // Handle Periodic Interrupt.

      if( read_byte( 0x40, 0xA0 ) != 0 ) {
        // Wait Interval (Int 15, AH=83) active.
        Bit32u time, toggle;

        time = read_dword( 0x40, 0x9C );  // Time left in microseconds.
        if( time < 0x3D1 ) {
          // Done waiting.
          Bit16u segment, offset;

          offset = read_word( 0x40, 0x98 );
          segment = read_word( 0x40, 0x9A );
          write_byte( 0x40, 0xA0, 0 );  // Turn of status byte.
          outb_cmos( 0xB, registerB & 0x37 ); // Clear the Periodic Interrupt.
          write_byte( segment, offset, 0x80 );  // Write to specified flag byte.
        } else {
          // Continue waiting.
          time -= 0x3D1;
          write_dword( 0x40, 0x9C, time );
        }
      }
    }
  }

ASM_START
  call eoi_both_pics
ASM_END
}


ASM_START
;------------------------------------------
;- INT74h : PS/2 mouse hardware interrupt -
;------------------------------------------
int74_handler:
  sti
  pusha
  push ds         ;; save DS
  push #0x00 ;; placeholder for status
  push #0x00 ;; placeholder for X
  push #0x00 ;; placeholder for Y
  push #0x00 ;; placeholder for Z
  push #0x00 ;; placeholder for make_far_call boolean
  call _int74_function
  pop  cx      ;; remove make_far_call from stack
  jcxz int74_done

  ;; make far call to EBDA:0022
  push #0x00
  pop ds
  push 0x040E     ;; push 0000:040E (opcodes 0xff, 0x36, 0x0E, 0x04)
  pop ds
  //CALL_EP(0x0022) ;; call far routine (call_Ep DS:0022 :opcodes 0xff, 0x1e, 0x22, 0x00)
  call far ptr[0x22]
int74_done:
  cli
  call eoi_both_pics
  add sp, #8     ;; pop status, x, y, z

  pop ds          ;; restore DS
  popa
  iret


;; This will perform an IRET, but will retain value of current CF
;; by altering flags on stack.  Better than RETF #02.
iret_modify_cf:
  jc   carry_set
  push bp
  mov  bp, sp
  and  BYTE [bp + 0x06], #0xfe
  pop  bp
  iret
carry_set:
  push bp
  mov  bp, sp
  or   BYTE [bp + 0x06], #0x01
  pop  bp
  iret


;----------------------
;- INT13h (relocated) -
;----------------------
;
; int13_relocated is a little bit messed up since I played with it
; I have to rewrite it:
;   - call a function that detect which function to call
;   - make all called C function get the same parameters list
;
int13_relocated:

#if BX_ELTORITO_BOOT
  ;; check for an eltorito function
  cmp   ah,#0x4a
  jb    int13_not_eltorito
  cmp   ah,#0x4d
  ja    int13_not_eltorito

  pusha
  push  es
  push  ds
  push  ss
  pop   ds

  push  #int13_out
  jmp   _int13_eltorito      ;; ELDX not used

int13_not_eltorito:
  push  ax
  push  bx
  push  cx
  push  dx

  ;; check if emulation active
  call  _cdemu_isactive
  cmp   al,#0x00
  je    int13_cdemu_inactive

  ;; check if access to the emulated drive
  call  _cdemu_emulated_drive
  pop   dx
  push  dx
  cmp   al,dl                ;; int13 on emulated drive
  jne   int13_nocdemu

  pop   dx
  pop   cx
  pop   bx
  pop   ax

  pusha
  push  es
  push  ds
  push  ss
  pop   ds

  push  #int13_out
  jmp   _int13_cdemu         ;; ELDX not used

int13_nocdemu:
  and   dl,#0xE0             ;; mask to get device class, including cdroms
  cmp   al,dl                ;; al is 0x00 or 0x80
  jne   int13_cdemu_inactive ;; inactive for device class

  pop   dx
  pop   cx
  pop   bx
  pop   ax

  push  ax
  push  cx
  push  dx
  push  bx

  dec   dl                   ;; real drive is dl - 1
  jmp   int13_legacy

int13_cdemu_inactive:
  pop   dx
  pop   cx
  pop   bx
  pop   ax

#endif // BX_ELTORITO_BOOT

int13_noeltorito:

  push  ax
  push  cx
  push  dx
  push  bx

int13_legacy:

  push  dx                   ;; push eltorito value of dx instead of sp

  push  bp
  push  si
  push  di

  push  es
  push  ds
  push  ss
  pop   ds

  ;; now the 16-bit registers can be restored with:
  ;; pop ds; pop es; popa; iret
  ;; arguments passed to functions should be
  ;; DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS

  test  dl, #0x80
  jnz   int13_notfloppy

  push #int13_out
  jmp _int13_diskette_function

int13_notfloppy:

#if BX_USE_ATADRV

  cmp   dl, #0xE0
  jb    int13_notcdrom

  // ebx is modified: BSD 5.2.1 boot loader problem
  // someone should figure out which 32 bit register that actually are used

  shr   ebx, #16
  push  bx

  call  _int13_cdrom

  pop   bx
  shl   ebx, #16

  jmp int13_out

int13_notcdrom:

#endif

int13_disk:
  call  _int13_harddisk

int13_out:
  pop ds
  pop es
  popa
  iret 

;----------
;- INT18h -
;----------
int18_handler: ;; Boot Failure recovery: try the next device.

  ;; Reset SP and SS
  mov  ax, #0xfffe
  mov  sp, ax
  xor  ax, ax
  mov  ss, ax

  ;; Get the boot sequence number out of the IPL memory
  ;; The first time we do this it will have been set to -1 so 
  ;; we will start from device 0.
  mov  bx, #IPL_SEG 
  mov  ds, bx                     ;; Set segment
  mov  bx, IPL_SEQUENCE_OFFSET    ;; BX is now the sequence number
  inc  bx                         ;; ++
  mov  IPL_SEQUENCE_OFFSET, bx    ;; Write it back
  mov  ds, ax                     ;; and reset the segment to zero. 

  ;; Call the C code for the next boot device
  push bx
  call _int18_function

  ;; Boot failed: invoke the boot recovery function...
  int  #0x18

;----------
;- INT19h -
;----------
int19_relocated: ;; Boot function, relocated
  ;;
  ;; *** Warning: INT 19h resets the whole machine *** 
  ;;
  ;; Because PV drivers in HVM guests detach some of the emulated devices, 
  ;; it is not safe to do a soft reboot by just dropping to real mode and
  ;; invoking INT 19h -- the boot drives might have disappeared!
  ;; If the user asks for a soft reboot, the only thing we can do is 
  ;; reset the whole machine.  When it comes back up, the normal BIOS 
  ;; boot sequence will start, which is more or less the required behaviour.
  ;; 
  ;; Reset SP and SS
  mov  ax, #0xfffe
  mov  sp, ax
  xor  ax, ax
  mov  ss, ax
  call _machine_reset

;----------
;- INT1Ch -
;----------
int1c_handler: ;; User Timer Tick
  iret


;----------------------
;- POST: Floppy Drive -
;----------------------
floppy_drive_post:
  mov  ax, #0x0000
  mov  ds, ax

  mov  al, #0x00
  mov  0x043e, al ;; drive 0 & 1 uncalibrated, no interrupt has occurred

  mov  0x043f, al  ;; diskette motor status: read op, drive0, motors off

  mov  0x0440, al  ;; diskette motor timeout counter: not active
  mov  0x0441, al  ;; diskette controller status return code

  mov  0x0442, al  ;; disk & diskette controller status register 0
  mov  0x0443, al  ;; diskette controller status register 1
  mov  0x0444, al  ;; diskette controller status register 2
  mov  0x0445, al  ;; diskette controller cylinder number
  mov  0x0446, al  ;; diskette controller head number
  mov  0x0447, al  ;; diskette controller sector number
  mov  0x0448, al  ;; diskette controller bytes written

  mov  0x048b, al  ;; diskette configuration data

  ;; -----------------------------------------------------------------
  ;; (048F) diskette controller information
  ;;
  mov  al, #0x10   ;; get CMOS diskette drive type
  out  0x70, AL
  in   AL, 0x71
  mov  ah, al      ;; save byte to AH

look_drive0:
  shr  al, #4      ;; look at top 4 bits for drive 0
  jz   f0_missing  ;; jump if no drive0
  mov  bl, #0x07   ;; drive0 determined, multi-rate, has changed line
  jmp  look_drive1
f0_missing:
  mov  bl, #0x00   ;; no drive0

look_drive1:
  mov  al, ah      ;; restore from AH
  and  al, #0x0f   ;; look at bottom 4 bits for drive 1
  jz   f1_missing  ;; jump if no drive1
  or   bl, #0x70   ;; drive1 determined, multi-rate, has changed line
f1_missing:
                   ;; leave high bits in BL zerod
  mov  0x048f, bl  ;; put new val in BDA (diskette controller information)
  ;; -----------------------------------------------------------------

  mov  al, #0x00
  mov  0x0490, al  ;; diskette 0 media state
  mov  0x0491, al  ;; diskette 1 media state

                   ;; diskette 0,1 operational starting state
                   ;; drive type has not been determined,
                   ;; has no changed detection line
  mov  0x0492, al
  mov  0x0493, al

  mov  0x0494, al  ;; diskette 0 current cylinder
  mov  0x0495, al  ;; diskette 1 current cylinder

  mov  al, #0x02
  out  #0x0a, al   ;; clear DMA-1 channel 2 mask bit

  SET_INT_VECTOR(0x1E, #0xF000, #diskette_param_table2)
  SET_INT_VECTOR(0x40, #0xF000, #int13_diskette)
  SET_INT_VECTOR(0x0E, #0xF000, #int0e_handler) ;; IRQ 6

  ret


;--------------------
;- POST: HARD DRIVE -
;--------------------
; relocated here because the primary POST area isnt big enough.
hard_drive_post:
  // IRQ 14 = INT 76h
  // INT 76h calls INT 15h function ax=9100

  mov  al, #0x0a   ; 0000 1010 = reserved, disable IRQ 14
  mov  dx, #0x03f6
  out  dx, al

  mov  ax, #0x0000
  mov  ds, ax
  mov  0x0474, al /* hard disk status of last operation */
  mov  0x0477, al /* hard disk port offset (XT only ???) */
  mov  0x048c, al /* hard disk status register */
  mov  0x048d, al /* hard disk error register */
  mov  0x048e, al /* hard disk task complete flag */
  mov  al, #0x01
  mov  0x0475, al /* hard disk number attached */
  mov  al, #0xc0
  mov  0x0476, al /* hard disk control byte */
  SET_INT_VECTOR(0x13, #0xF000, #int13_handler)
  SET_INT_VECTOR(0x76, #0xF000, #int76_handler)
  ;; INT 41h: hard disk 0 configuration pointer
  ;; INT 46h: hard disk 1 configuration pointer
  SET_INT_VECTOR(0x41, #EBDA_SEG, #0x003D)
  SET_INT_VECTOR(0x46, #EBDA_SEG, #0x004D)

  ;; move disk geometry data from CMOS to EBDA disk parameter table(s)
  mov  al, #0x12
  out  #0x70, al
  in   al, #0x71
  and  al, #0xf0
  cmp  al, #0xf0
  je   post_d0_extended
  jmp check_for_hd1
post_d0_extended:
  mov  al, #0x19
  out  #0x70, al
  in   al, #0x71
  cmp  al, #47  ;; decimal 47 - user definable
  je   post_d0_type47
  HALT(__LINE__)
post_d0_type47:
  ;; CMOS  purpose                  param table offset
  ;; 1b    cylinders low            0
  ;; 1c    cylinders high           1
  ;; 1d    heads                    2
  ;; 1e    write pre-comp low       5
  ;; 1f    write pre-comp high      6
  ;; 20    retries/bad map/heads>8  8
  ;; 21    landing zone low         C
  ;; 22    landing zone high        D
  ;; 23    sectors/track            E

  mov  ax, #EBDA_SEG
  mov  ds, ax

  ;;; Filling EBDA table for hard disk 0.
  mov  al, #0x1f
  out  #0x70, al
  in   al, #0x71
  mov  ah, al
  mov  al, #0x1e
  out  #0x70, al
  in   al, #0x71
  mov   (0x003d + 0x05), ax ;; write precomp word

  mov  al, #0x20
  out  #0x70, al
  in   al, #0x71
  mov   (0x003d + 0x08), al ;; drive control byte

  mov  al, #0x22
  out  #0x70, al
  in   al, #0x71
  mov  ah, al
  mov  al, #0x21
  out  #0x70, al
  in   al, #0x71
  mov   (0x003d + 0x0C), ax ;; landing zone word

  mov  al, #0x1c   ;; get cylinders word in AX
  out  #0x70, al
  in   al, #0x71   ;; high byte
  mov  ah, al
  mov  al, #0x1b
  out  #0x70, al
  in   al, #0x71   ;; low byte
  mov  bx, ax      ;; BX = cylinders

  mov  al, #0x1d
  out  #0x70, al
  in   al, #0x71
  mov  cl, al      ;; CL = heads

  mov  al, #0x23
  out  #0x70, al
  in   al, #0x71
  mov  dl, al      ;; DL = sectors

  cmp  bx, #1024
  jnbe hd0_post_logical_chs ;; if cylinders > 1024, use translated style CHS

hd0_post_physical_chs:
  ;; no logical CHS mapping used, just physical CHS
  ;; use Standard Fixed Disk Parameter Table (FDPT)
  mov   (0x003d + 0x00), bx ;; number of physical cylinders
  mov   (0x003d + 0x02), cl ;; number of physical heads
  mov   (0x003d + 0x0E), dl ;; number of physical sectors
  jmp check_for_hd1

hd0_post_logical_chs:
  ;; complies with Phoenix style Translated Fixed Disk Parameter Table (FDPT)
  mov   (0x003d + 0x09), bx ;; number of physical cylinders
  mov   (0x003d + 0x0b), cl ;; number of physical heads
  mov   (0x003d + 0x04), dl ;; number of physical sectors
  mov   (0x003d + 0x0e), dl ;; number of logical sectors (same)
  mov al, #0xa0
  mov   (0x003d + 0x03), al ;; A0h signature, indicates translated table

  cmp bx, #2048
  jnbe hd0_post_above_2048
  ;; 1024 < c <= 2048 cylinders
  shr bx, #0x01
  shl cl, #0x01
  jmp hd0_post_store_logical

hd0_post_above_2048:
  cmp bx, #4096
  jnbe hd0_post_above_4096
  ;; 2048 < c <= 4096 cylinders
  shr bx, #0x02
  shl cl, #0x02
  jmp hd0_post_store_logical

hd0_post_above_4096:
  cmp bx, #8192
  jnbe hd0_post_above_8192
  ;; 4096 < c <= 8192 cylinders
  shr bx, #0x03
  shl cl, #0x03
  jmp hd0_post_store_logical

hd0_post_above_8192:
  ;; 8192 < c <= 16384 cylinders
  shr bx, #0x04
  shl cl, #0x04

hd0_post_store_logical:
  mov   (0x003d + 0x00), bx ;; number of physical cylinders
  mov   (0x003d + 0x02), cl ;; number of physical heads
  ;; checksum
  mov   cl, #0x0f     ;; repeat count
  mov   si, #0x003d   ;; offset to disk0 FDPT
  mov   al, #0x00     ;; sum
hd0_post_checksum_loop:
  add   al, [si]
  inc   si
  dec   cl
  jnz hd0_post_checksum_loop
  not   al  ;; now take 2s complement
  inc   al
  mov   [si], al
;;; Done filling EBDA table for hard disk 0.


check_for_hd1:
  ;; is there really a second hard disk?  if not, return now
  mov  al, #0x12
  out  #0x70, al
  in   al, #0x71
  and  al, #0x0f
  jnz   post_d1_exists
  ret
post_d1_exists:
  ;; check that the hd type is really 0x0f.
  cmp al, #0x0f
  jz post_d1_extended
  HALT(__LINE__)
post_d1_extended:
  ;; check that the extended type is 47 - user definable
  mov  al, #0x1a
  out  #0x70, al
  in   al, #0x71
  cmp  al, #47  ;; decimal 47 - user definable
  je   post_d1_type47
  HALT(__LINE__)
post_d1_type47:
  ;; Table for disk1.
  ;; CMOS  purpose                  param table offset
  ;; 0x24    cylinders low            0
  ;; 0x25    cylinders high           1
  ;; 0x26    heads                    2
  ;; 0x27    write pre-comp low       5
  ;; 0x28    write pre-comp high      6
  ;; 0x29    heads>8                  8
  ;; 0x2a    landing zone low         C
  ;; 0x2b    landing zone high        D
  ;; 0x2c    sectors/track            E
;;; Fill EBDA table for hard disk 1.
  mov  ax, #EBDA_SEG
  mov  ds, ax
  mov  al, #0x28
  out  #0x70, al
  in   al, #0x71
  mov  ah, al
  mov  al, #0x27
  out  #0x70, al
  in   al, #0x71
  mov   (0x004d + 0x05), ax ;; write precomp word

  mov  al, #0x29
  out  #0x70, al
  in   al, #0x71
  mov   (0x004d + 0x08), al ;; drive control byte

  mov  al, #0x2b
  out  #0x70, al
  in   al, #0x71
  mov  ah, al
  mov  al, #0x2a
  out  #0x70, al
  in   al, #0x71
  mov   (0x004d + 0x0C), ax ;; landing zone word

  mov  al, #0x25   ;; get cylinders word in AX
  out  #0x70, al
  in   al, #0x71   ;; high byte
  mov  ah, al
  mov  al, #0x24
  out  #0x70, al
  in   al, #0x71   ;; low byte
  mov  bx, ax      ;; BX = cylinders

  mov  al, #0x26
  out  #0x70, al
  in   al, #0x71
  mov  cl, al      ;; CL = heads

  mov  al, #0x2c
  out  #0x70, al
  in   al, #0x71
  mov  dl, al      ;; DL = sectors

  cmp  bx, #1024
  jnbe hd1_post_logical_chs ;; if cylinders > 1024, use translated style CHS

hd1_post_physical_chs:
  ;; no logical CHS mapping used, just physical CHS
  ;; use Standard Fixed Disk Parameter Table (FDPT)
  mov   (0x004d + 0x00), bx ;; number of physical cylinders
  mov   (0x004d + 0x02), cl ;; number of physical heads
  mov   (0x004d + 0x0E), dl ;; number of physical sectors
  ret

hd1_post_logical_chs:
  ;; complies with Phoenix style Translated Fixed Disk Parameter Table (FDPT)
  mov   (0x004d + 0x09), bx ;; number of physical cylinders
  mov   (0x004d + 0x0b), cl ;; number of physical heads
  mov   (0x004d + 0x04), dl ;; number of physical sectors
  mov   (0x004d + 0x0e), dl ;; number of logical sectors (same)
  mov al, #0xa0
  mov   (0x004d + 0x03), al ;; A0h signature, indicates translated table

  cmp bx, #2048
  jnbe hd1_post_above_2048
  ;; 1024 < c <= 2048 cylinders
  shr bx, #0x01
  shl cl, #0x01
  jmp hd1_post_store_logical

hd1_post_above_2048:
  cmp bx, #4096
  jnbe hd1_post_above_4096
  ;; 2048 < c <= 4096 cylinders
  shr bx, #0x02
  shl cl, #0x02
  jmp hd1_post_store_logical

hd1_post_above_4096:
  cmp bx, #8192
  jnbe hd1_post_above_8192
  ;; 4096 < c <= 8192 cylinders
  shr bx, #0x03
  shl cl, #0x03
  jmp hd1_post_store_logical

hd1_post_above_8192:
  ;; 8192 < c <= 16384 cylinders
  shr bx, #0x04
  shl cl, #0x04

hd1_post_store_logical:
  mov   (0x004d + 0x00), bx ;; number of physical cylinders
  mov   (0x004d + 0x02), cl ;; number of physical heads
  ;; checksum
  mov   cl, #0x0f     ;; repeat count
  mov   si, #0x004d   ;; offset to disk0 FDPT
  mov   al, #0x00     ;; sum
hd1_post_checksum_loop:
  add   al, [si]
  inc   si
  dec   cl
  jnz hd1_post_checksum_loop
  not   al  ;; now take 2s complement
  inc   al
  mov   [si], al
;;; Done filling EBDA table for hard disk 1.

  ret

;--------------------
;- POST: EBDA segment
;--------------------
; relocated here because the primary POST area isnt big enough.
ebda_post:
#if BX_USE_EBDA
  mov ax, #EBDA_SEG
  mov ds, ax
  mov byte ptr [0x0], #EBDA_SIZE
#endif
  xor ax, ax            ; mov EBDA seg into 40E
  mov ds, ax
  mov word ptr [0x40E], #EBDA_SEG
  ret;;

;--------------------
;- POST: EOI + jmp via [0x40:67)
;--------------------
; relocated here because the primary POST area isnt big enough.
eoi_jmp_post:
  call eoi_both_pics

  xor ax, ax
  mov ds, ax

  jmp far ptr [0x467]


;--------------------
eoi_both_pics:
  mov   al, #0x20
  out   #0xA0, al ;; slave  PIC EOI
eoi_master_pic:
  mov   al, #0x20
  out   #0x20, al ;; master PIC EOI
  ret

;--------------------
BcdToBin:
  ;; in:  AL in BCD format
  ;; out: AL in binary format, AH will always be 0
  ;; trashes BX
  mov  bl, al
  and  bl, #0x0f ;; bl has low digit
  shr  al, #4    ;; al has high digit
  mov  bh, #10
  mul  al, bh    ;; multiply high digit by 10 (result in AX)
  add  al, bl    ;;   then add low digit
  ret

;--------------------
timer_tick_post:
  ;; Setup the Timer Ticks Count (0x46C:dword) and
  ;;   Timer Ticks Roller Flag (0x470:byte)
  ;; The Timer Ticks Count needs to be set according to
  ;; the current CMOS time, as if ticks have been occurring
  ;; at 18.2hz since midnight up to this point.  Calculating
  ;; this is a little complicated.  Here are the factors I gather
  ;; regarding this.  14,318,180 hz was the original clock speed,
  ;; chosen so it could be divided by either 3 to drive the 5Mhz CPU
  ;; at the time, or 4 to drive the CGA video adapter.  The div3
  ;; source was divided again by 4 to feed a 1.193Mhz signal to
  ;; the timer.  With a maximum 16bit timer count, this is again
  ;; divided down by 65536 to 18.2hz.
  ;;
  ;; 14,318,180 Hz clock
  ;;   /3 = 4,772,726 Hz fed to orginal 5Mhz CPU
  ;;   /4 = 1,193,181 Hz fed to timer
  ;;   /65536 (maximum timer count) = 18.20650736 ticks/second
  ;; 1 second = 18.20650736 ticks
  ;; 1 minute = 1092.390442 ticks
  ;; 1 hour   = 65543.42651 ticks
  ;;
  ;; Given the values in the CMOS clock, one could calculate
  ;; the number of ticks by the following:
  ;;   ticks = (BcdToBin(seconds) * 18.206507) +
  ;;           (BcdToBin(minutes) * 1092.3904)
  ;;           (BcdToBin(hours)   * 65543.427)
  ;; To get a little more accuracy, since Im using integer
  ;; arithmatic, I use:
  ;;   ticks = (BcdToBin(seconds) * 18206507) / 1000000 +
  ;;           (BcdToBin(minutes) * 10923904) / 10000 +
  ;;           (BcdToBin(hours)   * 65543427) / 1000

  ;; assuming DS=0000

  ;; get CMOS seconds
  xor  eax, eax ;; clear EAX
  mov  al, #0x00
  out  #0x70, al
  in   al, #0x71 ;; AL has CMOS seconds in BCD
  call BcdToBin  ;; EAX now has seconds in binary
  mov  edx, #18206507
  mul  eax, edx
  mov  ebx, #1000000
  xor  edx, edx
  div  eax, ebx
  mov  ecx, eax  ;; ECX will accumulate total ticks

  ;; get CMOS minutes
  xor  eax, eax ;; clear EAX
  mov  al, #0x02
  out  #0x70, al
  in   al, #0x71 ;; AL has CMOS minutes in BCD
  call BcdToBin  ;; EAX now has minutes in binary
  mov  edx, #10923904
  mul  eax, edx
  mov  ebx, #10000
  xor  edx, edx
  div  eax, ebx
  add  ecx, eax  ;; add to total ticks

  ;; get CMOS hours
  xor  eax, eax ;; clear EAX
  mov  al, #0x04
  out  #0x70, al
  in   al, #0x71 ;; AL has CMOS hours in BCD
  call BcdToBin  ;; EAX now has hours in binary
  mov  edx, #65543427
  mul  eax, edx
  mov  ebx, #1000
  xor  edx, edx
  div  eax, ebx
  add  ecx, eax  ;; add to total ticks

  mov  0x46C, ecx ;; Timer Ticks Count
  xor  al, al
  mov  0x470, al  ;; Timer Ticks Rollover Flag
  ret

;--------------------
int76_handler:
  ;; record completion in BIOS task complete flag
  push  ax
  push  ds
  mov   ax, #0x0040
  mov   ds, ax
  mov   0x008E, #0xff
  call  eoi_both_pics
  pop   ds
  pop   ax
  iret


;--------------------
#if BX_APM

use32 386
#define APM_PROT32
#include "apmbios.S"

use16 386
#define APM_PROT16
#include "apmbios.S"

#define APM_REAL
#include "apmbios.S"

#endif

ASM_END
#include "32bitgateway.c"
ASM_START

;--------------------
#if BX_PCIBIOS
use32 386
.align 16
bios32_structure:
  db 0x5f, 0x33, 0x32, 0x5f  ;; "_32_" signature
  dw bios32_entry_point, 0xf ;; 32 bit physical address
  db 0             ;; revision level
  ;; length in paragraphs and checksum stored in a word to prevent errors
  dw (~(((bios32_entry_point >> 8) + (bios32_entry_point & 0xff) + 0x32) \
        & 0xff) << 8) + 0x01
  db 0,0,0,0,0     ;; reserved

.align 16
bios32_entry_point:
  pushf
  cmp eax, #0x49435024
  jne unknown_service
  mov eax, #0x80000000
  mov dx, #0x0cf8
  out dx, eax
  mov dx, #0x0cfc
  in  eax, dx
  cmp eax, #0x12378086
  jne unknown_service
  mov ebx, #0x000f0000
  mov ecx, #0
  mov edx, #pcibios_protected
  xor al, al
  jmp bios32_end
unknown_service:
  mov al, #0x80
bios32_end:
  popf
  retf

.align 16
pcibios_protected:
  pushf
  cli
  push esi
  push edi
  cmp al, #0x01 ;; installation check
  jne pci_pro_f02
  mov bx, #0x0210
  mov cx, #0
  mov edx, #0x20494350
  mov al, #0x01
  jmp pci_pro_ok
pci_pro_f02: ;; find pci device
  cmp al, #0x02
  jne pci_pro_f08
  shl ecx, #16
  mov cx, dx
  mov bx, #0x0000
  mov di, #0x00
pci_pro_devloop:
  call pci_pro_select_reg
  mov dx, #0x0cfc
  in  eax, dx
  cmp eax, ecx
  jne pci_pro_nextdev
  cmp si, #0
  je  pci_pro_ok
  dec si
pci_pro_nextdev:
  inc bx
  cmp bx, #0x0100
  jne pci_pro_devloop
  mov ah, #0x86
  jmp pci_pro_fail
pci_pro_f08: ;; read configuration byte
  cmp al, #0x08
  jne pci_pro_f09
  call pci_pro_select_reg
  push edx
  mov dx, di
  and dx, #0x03
  add dx, #0x0cfc
  in  al, dx
  pop edx
  mov cl, al
  jmp pci_pro_ok
pci_pro_f09: ;; read configuration word
  cmp al, #0x09
  jne pci_pro_f0a
  call pci_pro_select_reg
  push edx
  mov dx, di
  and dx, #0x02
  add dx, #0x0cfc
  in  ax, dx
  pop edx
  mov cx, ax
  jmp pci_pro_ok
pci_pro_f0a: ;; read configuration dword
  cmp al, #0x0a
  jne pci_pro_f0b
  call pci_pro_select_reg
  push edx
  mov dx, #0x0cfc
  in  eax, dx
  pop edx
  mov ecx, eax
  jmp pci_pro_ok
pci_pro_f0b: ;; write configuration byte
  cmp al, #0x0b
  jne pci_pro_f0c
  call pci_pro_select_reg
  push edx
  mov dx, di
  and dx, #0x03
  add dx, #0x0cfc
  mov al, cl
  out dx, al
  pop edx
  jmp pci_pro_ok
pci_pro_f0c: ;; write configuration word
  cmp al, #0x0c
  jne pci_pro_f0d
  call pci_pro_select_reg
  push edx
  mov dx, di
  and dx, #0x02
  add dx, #0x0cfc
  mov ax, cx
  out dx, ax
  pop edx
  jmp pci_pro_ok
pci_pro_f0d: ;; write configuration dword
  cmp al, #0x0d
  jne pci_pro_unknown
  call pci_pro_select_reg
  push edx
  mov dx, #0x0cfc
  mov eax, ecx
  out dx, eax
  pop edx
  jmp pci_pro_ok
pci_pro_unknown:
  mov ah, #0x81
pci_pro_fail:
  pop edi
  pop esi
  sti
  popf
  stc
  retf
pci_pro_ok:
  xor ah, ah
  pop edi
  pop esi
  sti
  popf
  clc
  retf

pci_pro_select_reg:
  push edx
  mov eax, #0x800000
  mov ax,  bx
  shl eax, #8
  and di,  #0xff
  or  ax,  di
  and al,  #0xfc
  mov dx, #0x0cf8
  out dx,  eax
  pop edx
  ret

use16 386

pcibios_real:
  push eax
  push dx
  mov eax, #0x80000000
  mov dx, #0x0cf8
  out dx, eax
  mov dx, #0x0cfc
  in  eax, dx
  cmp eax, #0x12378086
  je  pci_present
  pop dx
  pop eax
  mov ah, #0xff
  stc
  ret
pci_present:
  pop dx
  pop eax
  cmp al, #0x01 ;; installation check
  jne pci_real_f02
  mov ax, #0x0001
  mov bx, #0x0210
  mov cx, #0
  mov edx, #0x20494350
  mov edi, #0xf0000
  mov di, #pcibios_protected
  clc
  ret
pci_real_f02: ;; find pci device
  push esi
  push edi
  cmp al, #0x02
  jne pci_real_f08
  shl ecx, #16
  mov cx, dx
  mov bx, #0x0000
  mov di, #0x00
pci_real_devloop:
  call pci_real_select_reg
  mov dx, #0x0cfc
  in  eax, dx
  cmp eax, ecx
  jne pci_real_nextdev
  cmp si, #0
  je  pci_real_ok
  dec si
pci_real_nextdev:
  inc bx
  cmp bx, #0x0100
  jne pci_real_devloop
  mov dx, cx
  shr ecx, #16
  mov ah, #0x86
  jmp pci_real_fail
pci_real_f08: ;; read configuration byte
  cmp al, #0x08
  jne pci_real_f09
  call pci_real_select_reg
  push dx
  mov dx, di
  and dx, #0x03
  add dx, #0x0cfc
  in  al, dx
  pop dx
  mov cl, al
  jmp pci_real_ok
pci_real_f09: ;; read configuration word
  cmp al, #0x09
  jne pci_real_f0a
  call pci_real_select_reg
  push dx
  mov dx, di
  and dx, #0x02
  add dx, #0x0cfc
  in  ax, dx
  pop dx
  mov cx, ax
  jmp pci_real_ok
pci_real_f0a: ;; read configuration dword
  cmp al, #0x0a
  jne pci_real_f0b
  call pci_real_select_reg
  push dx
  mov dx, #0x0cfc
  in  eax, dx
  pop dx
  mov ecx, eax
  jmp pci_real_ok
pci_real_f0b: ;; write configuration byte
  cmp al, #0x0b
  jne pci_real_f0c
  call pci_real_select_reg
  push dx
  mov dx, di
  and dx, #0x03
  add dx, #0x0cfc
  mov al, cl
  out dx, al
  pop dx
  jmp pci_real_ok
pci_real_f0c: ;; write configuration word
  cmp al, #0x0c
  jne pci_real_f0d
  call pci_real_select_reg
  push dx
  mov dx, di
  and dx, #0x02
  add dx, #0x0cfc
  mov ax, cx
  out dx, ax
  pop dx
  jmp pci_real_ok
pci_real_f0d: ;; write configuration dword
  cmp al, #0x0d
  jne pci_real_unknown
  call pci_real_select_reg
  push dx
  mov dx, #0x0cfc
  mov eax, ecx
  out dx, eax
  pop dx
  jmp pci_real_ok
pci_real_unknown:
  mov ah, #0x81
pci_real_fail:
  pop edi
  pop esi
  stc
  ret
pci_real_ok:
  xor ah, ah
  pop edi
  pop esi
  clc
  ret

pci_real_select_reg:
  push dx
  mov eax, #0x800000
  mov ax,  bx
  shl eax, #8
  and di,  #0xff
  or  ax,  di
  and al,  #0xfc
  mov dx,  #0x0cf8
  out dx,  eax
  pop dx
  ret
  
.align 16
pci_routing_table_structure:
  db 0x24, 0x50, 0x49, 0x52  ;; "$PIR" signature
  db 0, 1 ;; version
  dw 32 + (6 * 16) ;; table size
  db 0 ;; PCI interrupt router bus
  db 0x08 ;; PCI interrupt router DevFunc
  dw 0x0000 ;; PCI exclusive IRQs 
  dw 0x8086 ;; compatible PCI interrupt router vendor ID
  dw 0x7000 ;; compatible PCI interrupt router device ID
  dw 0,0 ;; Miniport data
  db 0,0,0,0,0,0,0,0,0,0,0 ;; reserved
  db 0x07 ;; checksum
  ;; first slot entry PCI-to-ISA (embedded)
  db 0 ;; pci bus number
  db 0x08 ;; pci device number (bit 7-3)
  db 0x61 ;; link value INTA#: pointer into PCI2ISA config space
  dw 0x0ca0 ;; IRQ bitmap INTA# 
  db 0x62 ;; link value INTB#
  dw 0x0ca0 ;; IRQ bitmap INTB# 
  db 0x63 ;; link value INTC#
  dw 0x0ca0 ;; IRQ bitmap INTC# 
  db 0x60 ;; link value INTD#
  dw 0x0ca0 ;; IRQ bitmap INTD#
  db 0 ;; physical slot (0 = embedded)
  db 0 ;; reserved
  ;; second slot entry: 1st PCI slot
  db 0 ;; pci bus number
  db 0x10 ;; pci device number (bit 7-3)
  db 0x62 ;; link value INTA#
  dw 0x0ca0 ;; IRQ bitmap INTA# 
  db 0x63 ;; link value INTB#
  dw 0x0ca0 ;; IRQ bitmap INTB# 
  db 0x60 ;; link value INTC#
  dw 0x0ca0 ;; IRQ bitmap INTC# 
  db 0x61 ;; link value INTD#
  dw 0x0ca0 ;; IRQ bitmap INTD#
  db 1 ;; physical slot (0 = embedded)
  db 0 ;; reserved
  ;; third slot entry: 2nd PCI slot
  db 0 ;; pci bus number
  db 0x18 ;; pci device number (bit 7-3)
  db 0x63 ;; link value INTA#
  dw 0x0ca0 ;; IRQ bitmap INTA# 
  db 0x60 ;; link value INTB#
  dw 0x0ca0 ;; IRQ bitmap INTB# 
  db 0x61 ;; link value INTC#
  dw 0x0ca0 ;; IRQ bitmap INTC# 
  db 0x62 ;; link value INTD#
  dw 0x0ca0 ;; IRQ bitmap INTD#
  db 2 ;; physical slot (0 = embedded)
  db 0 ;; reserved
  ;; 4th slot entry: 3rd PCI slot
  db 0 ;; pci bus number
  db 0x20 ;; pci device number (bit 7-3)
  db 0x60 ;; link value INTA#
  dw 0x0ca0 ;; IRQ bitmap INTA# 
  db 0x61 ;; link value INTB#
  dw 0x0ca0 ;; IRQ bitmap INTB# 
  db 0x62 ;; link value INTC#
  dw 0x0ca0 ;; IRQ bitmap INTC# 
  db 0x63 ;; link value INTD#
  dw 0x0ca0 ;; IRQ bitmap INTD#
  db 3 ;; physical slot (0 = embedded)
  db 0 ;; reserved
  ;; 5th slot entry: 4rd PCI slot
  db 0 ;; pci bus number
  db 0x28 ;; pci device number (bit 7-3)
  db 0x61 ;; link value INTA#
  dw 0x0ca0 ;; IRQ bitmap INTA# 
  db 0x62 ;; link value INTB#
  dw 0x0ca0 ;; IRQ bitmap INTB# 
  db 0x63 ;; link value INTC#
  dw 0x0ca0 ;; IRQ bitmap INTC# 
  db 0x60 ;; link value INTD#
  dw 0x0ca0 ;; IRQ bitmap INTD#
  db 4 ;; physical slot (0 = embedded)
  db 0 ;; reserved
  ;; 6th slot entry: 5rd PCI slot
  db 0 ;; pci bus number
  db 0x30 ;; pci device number (bit 7-3)
  db 0x62 ;; link value INTA#
  dw 0x0ca0 ;; IRQ bitmap INTA# 
  db 0x63 ;; link value INTB#
  dw 0x0ca0 ;; IRQ bitmap INTB# 
  db 0x60 ;; link value INTC#
  dw 0x0ca0 ;; IRQ bitmap INTC# 
  db 0x61 ;; link value INTD#
  dw 0x0ca0 ;; IRQ bitmap INTD#
  db 5 ;; physical slot (0 = embedded)
  db 0 ;; reserved
#endif // BX_PCIBIOS

; parallel port detection: base address in DX, index in BX, timeout in CL
detect_parport:
  push dx
  add  dx, #2
  in   al, dx
  and  al, #0xdf ; clear input mode
  out  dx, al
  pop  dx
  mov  al, #0xaa
  out  dx, al
  in   al, dx
  cmp  al, #0xaa
  jne  no_parport
  push bx
  shl  bx, #1
  mov  [bx+0x408], dx ; Parallel I/O address
  pop  bx
  mov  [bx+0x478], cl ; Parallel printer timeout
  inc  bx
no_parport:
  ret

; serial port detection: base address in DX, index in BX, timeout in CL
detect_serial:
  push dx
  inc  dx
  mov  al, #0x02
  out  dx, al
  in   al, dx
  cmp  al, #0x02
  jne  no_serial
  inc  dx
  in   al, dx
  cmp  al, #0x02
  jne  no_serial
  dec  dx
  xor  al, al
  out  dx, al
  pop  dx
  push bx
  shl  bx, #1
  mov  [bx+0x400], dx ; Serial I/O address
  pop  bx
  mov  [bx+0x47c], cl ; Serial timeout
  inc  bx
  ret
no_serial:
  pop  dx
  ret

rom_checksum:
  push ax
  push bx
  push cx
  xor  ax, ax
  xor  bx, bx
  xor  cx, cx
  mov  ch, [2]
  shl  cx, #1
checksum_loop:
  add  al, [bx]
  inc  bx
  loop checksum_loop
  and  al, #0xff
  pop  cx
  pop  bx
  pop  ax
  ret


;; We need a copy of this string, but we are not actually a PnP BIOS, 
;; so make sure it is *not* aligned, so OSes will not see it if they scan.
.align 16
  db 0
pnp_string:
  .ascii "$PnP"


rom_scan:
  ;; Scan for existence of valid expansion ROMS.
  ;;   Video ROM:   from 0xC0000..0xC7FFF in 2k increments
  ;;   General ROM: from 0xC8000..0xDFFFF in 2k increments
  ;;   System  ROM: only 0xE0000
  ;;
  ;; Header:
  ;;   Offset    Value
  ;;   0         0x55
  ;;   1         0xAA
  ;;   2         ROM length in 512-byte blocks
  ;;   3         ROM initialization entry point (FAR CALL)

#if BX_TCGBIOS
  call _tcpa_start_option_rom_scan    /* specs: 3.2.3.3 + 10.4.3 */
#endif
  mov  cx, #0xc000
rom_scan_loop:
  mov  ds, cx
  mov  ax, #0x0004 ;; start with increment of 4 (512-byte) blocks = 2k
  cmp [0], #0xAA55 ;; look for signature
  jne  rom_scan_increment
  call rom_checksum
  jnz  rom_scan_increment
  mov  al, [2]  ;; change increment to ROM length in 512-byte blocks

  ;; We want our increment in 512-byte quantities, rounded to
  ;; the nearest 2k quantity, since we only scan at 2k intervals.
  test al, #0x03
  jz   block_count_rounded
  and  al, #0xfc ;; needs rounding up
  add  al, #0x04
block_count_rounded:

#if BX_TCGBIOS
  push ax
  push ds
  push ecx
  xor ax, ax
  mov ds, ax
  and ecx, #0xffff
  push ecx       ;; segment where option rom is located at
  call _tcpa_option_rom                   /* specs: 3.2.3.3 */
  add sp, #4    ;; pop segment
  pop ecx	;; original ecx
  pop ds
  pop ax
#endif
  xor  bx, bx   ;; Restore DS back to 0000:
  mov  ds, bx
  push ax       ;; Save AX
  push di       ;; Save DI
  ;; Push addr of ROM entry point
  push cx       ;; Push seg
  push #0x0003  ;; Push offset

  ;; Point ES:DI at "$PnP", which tells the ROM that we are a PnP BIOS.  
  ;; That should stop it grabbing INT 19h; we will use its BEV instead.
  mov  ax, #0xf000
  mov  es, ax
  lea  di, pnp_string 

  mov  bp, sp   ;; Call ROM init routine using seg:off on stack
  db   0xff     ;; call_far ss:[bp+0]
  db   0x5e
  db   0
  cli           ;; In case expansion ROM BIOS turns IF on
  add  sp, #2   ;; Pop offset value
  pop  cx       ;; Pop seg value (restore CX)

  ;; Look at the ROM's PnP Expansion header.  Properly, we're supposed 
  ;; to init all the ROMs and then go back and build an IPL table of 
  ;; all the bootable devices, but we can get away with one pass.
  mov  ds, cx       ;; ROM base
  mov  bx, 0x001a   ;; 0x1A is the offset into ROM header that contains...
  mov  ax, [bx]     ;; the offset of PnP expansion header, where...
  cmp  ax, #0x5024  ;; we look for signature "$PnP"
  jne  no_bev
  mov  ax, 2[bx]
  cmp  ax, #0x506e 
  jne  no_bev
  mov  ax, 0x1a[bx] ;; 0x1A is also the offset into the expansion header of...
  cmp  ax, #0x0000  ;; the Bootstrap Entry Vector, or zero if there is none.
  je   no_bev

  ;; Found a device that thinks it can boot the system.  Record its BEV.
  mov  bx, #IPL_SEG            ;; Go to the segment where the IPL table lives 
  mov  ds, bx
  mov  bx, IPL_COUNT_OFFSET    ;; Read the number of entries so far
  cmp  bx, #IPL_TABLE_ENTRIES
  je   no_bev                  ;; Get out if the table is full
  shl  bx, #0x4                ;; Turn count into offset (entries are 16 bytes)
  mov  0[bx], #0x80            ;; This entry is a BEV device
  mov  6[bx], cx               ;; Build a far pointer from the segment...
  mov  4[bx], ax               ;; and the offset
  shr  bx, #0x4                ;; Turn the offset back into a count
  inc  bx                      ;; We have one more entry now
  mov  IPL_COUNT_OFFSET, bx    ;; Remember that.

no_bev:
  pop  di       ;; Restore DI
  pop  ax       ;; Restore AX
rom_scan_increment:
  shl  ax, #5   ;; convert 512-bytes blocks to 16-byte increments
                ;; because the segment selector is shifted left 4 bits.
  add  cx, ax
  cmp  cx, #0xe000
  jbe  rom_scan_loop

  xor  ax, ax   ;; Restore DS back to 0000:
  mov  ds, ax
  ret

#ifdef HVMASSIST

; Copy the SMBIOS entry point from where hvmloader left it.
; The entry point must be somewhere in 0xf0000-0xfffff on a 16-byte boundary,
; but the tables themselves can be elsewhere.
smbios_init:
  push ax
  push cx
  push es
  push ds
  push di
  push si

  mov cx, #0x001f ; 0x1f bytes to copy
  mov ax, #0xf000
  mov es, ax      ; destination segment is 0xf0000
  mov di, #smbios_entry_point ; destination offset
  mov ax, #(SMBIOS_PHYSICAL_ADDRESS>>4)
  mov ds, ax
  mov si, #(SMBIOS_PHYSICAL_ADDRESS&15)
  cld
  rep
    movsb

  pop si
  pop di
  pop ds
  pop es
  pop cx
  pop ax

  ret

#endif



;; for 'C' strings and other data, insert them here with
;; a the following hack:
;; DATA_SEG_DEFS_HERE


;--------
;- POST -
;--------
.org 0xe05b ; POST Entry Point
post:

  xor ax, ax

  ;; first reset the DMA controllers
  out 0x0d,al
  out 0xda,al

  ;; then initialize the DMA controllers
  mov al, #0xC0
  out 0xD6, al ; cascade mode of channel 4 enabled
  mov al, #0x00
  out 0xD4, al ; unmask channel 4

  ;; Examine CMOS shutdown status.
  mov AL, #0x0f
  out 0x70, AL
  in  AL, 0x71

  ;; backup status
  mov bl, al

  ;; Reset CMOS shutdown status.
  mov AL, #0x0f
  out 0x70, AL          ; select CMOS register Fh
  mov AL, #0x00
  out 0x71, AL          ; set shutdown action to normal

  ;; Examine CMOS shutdown status.
  mov al, bl

  ;; 0x00, 0x09, 0x0D+ = normal startup
  cmp AL, #0x00
  jz normal_post
  cmp AL, #0x0d
  jae normal_post
  cmp AL, #0x09
  je normal_post

  ;; 0x05 = eoi + jmp via [0x40:0x67] jump
  cmp al, #0x05
  je  eoi_jmp_post

  ;; Examine CMOS shutdown status.
  ;;  0x01,0x02,0x03,0x04,0x06,0x07,0x08, 0x0a, 0x0b, 0x0c = Unimplemented shutdown status.
  push bx
  call _shutdown_status_panic

#if 0 
  HALT(__LINE__)
  ;
  ;#if 0
  ;  0xb0, 0x20,       /* mov al, #0x20 */
  ;  0xe6, 0x20,       /* out 0x20, al    ;send EOI to PIC */
  ;#endif
  ;
  pop es
  pop ds
  popa
  iret
#endif

normal_post:
  ; case 0: normal startup

  cli
  mov  ax, #0xfffe
  mov  sp, ax
  mov  ax, #0x0000
  mov  ds, ax
  mov  ss, ax

  ;; zero out BIOS data area (40:00..40:ff)
  mov  es, ax
  mov  cx, #0x0080 ;; 128 words
  mov  di, #0x0400
  cld
  rep
    stosw

  call _log_bios_start

  call _clobber_entry_point

  ;; set all interrupts to default handler
  mov  bx, #0x0000    ;; offset index
  mov  cx, #0x0100    ;; counter (256 interrupts)
  mov  ax, #dummy_iret_handler
  mov  dx, #0xF000

post_default_ints:
  mov  [bx], ax
  inc  bx
  inc  bx
  mov  [bx], dx
  inc  bx
  inc  bx
  loop post_default_ints

  ;; set vector 0x79 to zero
  ;; this is used by 'gardian angel' protection system
  SET_INT_VECTOR(0x79, #0, #0)

  ;; base memory in K 40:13 (word)
  mov  ax, #BASE_MEM_IN_K
  mov  0x0413, ax


  ;; Manufacturing Test 40:12
  ;;   zerod out above

  ;; Warm Boot Flag 0040:0072
  ;;   value of 1234h = skip memory checks
  ;;   zerod out above


  ;; Printer Services vector
  SET_INT_VECTOR(0x17, #0xF000, #int17_handler)

  ;; Bootstrap failure vector
  SET_INT_VECTOR(0x18, #0xF000, #int18_handler)

  ;; Bootstrap Loader vector
  SET_INT_VECTOR(0x19, #0xF000, #int19_handler)

  ;; User Timer Tick vector
  SET_INT_VECTOR(0x1c, #0xF000, #int1c_handler)

  ;; Memory Size Check vector
  SET_INT_VECTOR(0x12, #0xF000, #int12_handler)

  ;; Equipment Configuration Check vector
  SET_INT_VECTOR(0x11, #0xF000, #int11_handler)

  ;; System Services
  SET_INT_VECTOR(0x15, #0xF000, #int15_handler)

  ;; EBDA setup
  call ebda_post

  ;; PIT setup
  SET_INT_VECTOR(0x08, #0xF000, #int08_handler)
  ;; int 1C already points at dummy_iret_handler (above)
  mov al, #0x34 ; timer0: binary count, 16bit count, mode 2
  out 0x43, al
#ifdef HVMASSIST
  mov al, #0x0b ; #0xe90b = 20 Hz (temporary, until we fix xen/vmx support)
  out 0x40, al ; lsb
  mov al, #0xe9
  out 0x40, al ; msb
#else
  mov al, #0x00 ; maximum count of 0000H = 18.2Hz
  out 0x40, al
  out 0x40, al
#endif

  ;; Keyboard
  SET_INT_VECTOR(0x09, #0xF000, #int09_handler)
  SET_INT_VECTOR(0x16, #0xF000, #int16_handler)

  xor  ax, ax
  mov  ds, ax
  mov  0x0417, al /* keyboard shift flags, set 1 */
  mov  0x0418, al /* keyboard shift flags, set 2 */
  mov  0x0419, al /* keyboard alt-numpad work area */
  mov  0x0471, al /* keyboard ctrl-break flag */
  mov  0x0497, al /* keyboard status flags 4 */
  mov  al, #0x10
  mov  0x0496, al /* keyboard status flags 3 */


  /* keyboard head of buffer pointer */
  mov  bx, #0x001E
  mov  0x041A, bx

  /* keyboard end of buffer pointer */
  mov  0x041C, bx

  /* keyboard pointer to start of buffer */
  mov  bx, #0x001E
  mov  0x0480, bx

  /* keyboard pointer to end of buffer */
  mov  bx, #0x003E
  mov  0x0482, bx

  /* init the keyboard */
  call _keyboard_init

  ;; mov CMOS Equipment Byte to BDA Equipment Word
  mov  ax, 0x0410
  mov  al, #0x14
  out  0x70, al
  in   al, 0x71
  mov  0x0410, ax

#if BX_TCGBIOS
  call _tcpa_acpi_init

  push dword #0
  call _tcpa_initialize_tpm
  add sp, #4

  call _tcpa_do_measure_POSTs
  call _tcpa_wake_event     /* specs: 3.2.3.7 */
#endif

  ;; Parallel setup
  SET_INT_VECTOR(0x0F, #0xF000, #dummy_iret_handler)
  xor ax, ax
  mov ds, ax
  xor bx, bx
  mov cl, #0x14 ; timeout value
  mov dx, #0x378 ; Parallel I/O address, port 1
  call detect_parport
  mov dx, #0x278 ; Parallel I/O address, port 2
  call detect_parport
  shl bx, #0x0e
  mov ax, 0x410   ; Equipment word bits 14..15 determing # parallel ports
  and ax, #0x3fff
  or  ax, bx ; set number of parallel ports
  mov 0x410, ax

  ;; Serial setup
  SET_INT_VECTOR(0x0C, #0xF000, #dummy_iret_handler)
  SET_INT_VECTOR(0x14, #0xF000, #int14_handler)
  xor bx, bx
  mov cl, #0x0a ; timeout value
  mov dx, #0x03f8 ; Serial I/O address, port 1
  call detect_serial
  mov dx, #0x02f8 ; Serial I/O address, port 2
  call detect_serial
  mov dx, #0x03e8 ; Serial I/O address, port 3
  call detect_serial
  mov dx, #0x02e8 ; Serial I/O address, port 4
  call detect_serial
  shl bx, #0x09
  mov ax, 0x410   ; Equipment word bits 9..11 determing # serial ports
  and ax, #0xf1ff
  or  ax, bx ; set number of serial port
  mov 0x410, ax

  ;; CMOS RTC
  SET_INT_VECTOR(0x1A, #0xF000, #int1a_handler)
  SET_INT_VECTOR(0x4A, #0xF000, #dummy_iret_handler)
  SET_INT_VECTOR(0x70, #0xF000, #int70_handler)
  ;; BIOS DATA AREA 0x4CE ???
  call timer_tick_post

  ;; PS/2 mouse setup
  SET_INT_VECTOR(0x74, #0xF000, #int74_handler)

  ;; IRQ13 (FPU exception) setup
  SET_INT_VECTOR(0x75, #0xF000, #int75_handler)

  ;; Video setup
  SET_INT_VECTOR(0x10, #0xF000, #int10_handler)

  ;; PIC
  mov al, #0x11 ; send initialisation commands
  out 0x20, al
  out 0xa0, al
  mov al, #0x08
  out 0x21, al
  mov al, #0x70
  out 0xa1, al
  mov al, #0x04
  out 0x21, al
  mov al, #0x02
  out 0xa1, al
  mov al, #0x01
  out 0x21, al
  out 0xa1, al
  mov  al, #0xb8
  out  0x21, AL ;master pic: unmask IRQ 0, 1, 2, 6
#if BX_USE_PS2_MOUSE
  mov  al, #0x8f
#else
  mov  al, #0x9f
#endif
  out  0xa1, AL ;slave  pic: unmask IRQ 12, 13, 14

#ifdef HVMASSIST
  call _copy_e820_table
  call smbios_init
#endif

  call _init_boot_vectors

  call rom_scan

  call _print_bios_banner 

  ;;
  ;; Floppy setup
  ;;
  call floppy_drive_post

#if BX_USE_ATADRV

  ;;
  ;; Hard Drive setup
  ;;
  call hard_drive_post

  ;;
  ;; ATA/ATAPI driver setup
  ;;
  call _ata_init
  call _ata_detect
  ;;
#else // BX_USE_ATADRV

  ;;
  ;; Hard Drive setup
  ;;
  call hard_drive_post

#endif // BX_USE_ATADRV

#if BX_ELTORITO_BOOT
  ;;
  ;; eltorito floppy/harddisk emulation from cd
  ;;
  call _cdemu_init
  ;;
#endif // BX_ELTORITO_BOOT
 
#if BX_TCGBIOS
  call _tcpa_calling_int19h          /* specs: 8.2.3 step 1 */
  call _tcpa_add_event_separators    /* specs: 8.2.3 step 2 */
#endif

  ;; Start the boot sequence.   See the comments in int19_relocated 
  ;; for why we use INT 18h instead of INT 19h here.
  int  #0x18

#if BX_TCGBIOS
  call _tcpa_returned_int19h         /* specs: 8.2.3 step 3/7 */
#endif

.org 0xe2c3 ; NMI Handler Entry Point
nmi:
  ;; FIXME the NMI handler should not panic
  ;; but iret when called from int75 (fpu exception)
  call _nmi_handler_msg
  iret

int75_handler:
  out  0xf0, al         // clear irq13 
  call eoi_both_pics    // clear interrupt
  int  2                // legacy nmi call
  iret

;-------------------------------------------
;- INT 13h Fixed Disk Services Entry Point -
;-------------------------------------------
.org 0xe3fe ; INT 13h Fixed Disk Services Entry Point
int13_handler:
  //JMPL(int13_relocated)
  jmp int13_relocated

.org 0xe401 ; Fixed Disk Parameter Table

;----------
;- INT19h -
;----------
.org 0xe6f2 ; INT 19h Boot Load Service Entry Point
int19_handler:

  jmp int19_relocated
;-------------------------------------------
;- System BIOS Configuration Data Table
;-------------------------------------------
.org BIOS_CONFIG_TABLE
db 0x08                  ; Table size (bytes) -Lo
db 0x00                  ; Table size (bytes) -Hi
db SYS_MODEL_ID
db SYS_SUBMODEL_ID
db BIOS_REVISION
; Feature byte 1
; b7: 1=DMA channel 3 used by hard disk
; b6: 1=2 interrupt controllers present
; b5: 1=RTC present
; b4: 1=BIOS calls int 15h/4Fh every key
; b3: 1=wait for extern event supported (Int 15h/41h)
; b2: 1=extended BIOS data area used
; b1: 0=AT or ESDI bus, 1=MicroChannel
; b0: 1=Dual bus (MicroChannel + ISA)
db (0 << 7) | \
   (1 << 6) | \
   (1 << 5) | \
   (BX_CALL_INT15_4F << 4) | \
   (0 << 3) | \
   (BX_USE_EBDA << 2) | \
   (0 << 1) | \
   (0 << 0)
; Feature byte 2
; b7: 1=32-bit DMA supported
; b6: 1=int16h, function 9 supported
; b5: 1=int15h/C6h (get POS data) supported
; b4: 1=int15h/C7h (get mem map info) supported
; b3: 1=int15h/C8h (en/dis CPU) supported
; b2: 1=non-8042 kb controller
; b1: 1=data streaming supported
; b0: reserved
db (0 << 7) | \
   (1 << 6) | \
   (0 << 5) | \
   (0 << 4) | \
   (0 << 3) | \
   (0 << 2) | \
   (0 << 1) | \
   (0 << 0)
; Feature byte 3
; b7: not used
; b6: reserved
; b5: reserved
; b4: POST supports ROM-to-RAM enable/disable
; b3: SCSI on system board
; b2: info panel installed
; b1: Initial Machine Load (IML) system - BIOS on disk
; b0: SCSI supported in IML
db 0x00
; Feature byte 4
; b7: IBM private
; b6: EEPROM present
; b5-3: ABIOS presence (011 = not supported)
; b2: private
; b1: memory split above 16Mb supported
; b0: POSTEXT directly supported by POST
db 0x00
; Feature byte 5 (IBM)
; b1: enhanced mouse
; b0: flash EPROM
db 0x00



.org 0xe729 ; Baud Rate Generator Table

;----------
;- INT14h -
;----------
.org 0xe739 ; INT 14h Serial Communications Service Entry Point
int14_handler:
  push ds
  pusha
  mov  ax, #0x0000
  mov  ds, ax
  call _int14_function
  popa
  pop  ds
  iret


;----------------------------------------
;- INT 16h Keyboard Service Entry Point -
;----------------------------------------
.org 0xe82e
int16_handler:

  sti
  push  ds
  pushf
  pusha

  cmp   ah, #0x00
  je    int16_F00
  cmp   ah, #0x10
  je    int16_F00

  mov  bx, #0xf000
  mov  ds, bx
  call _int16_function
  popa
  popf
  pop  ds
  jz   int16_zero_set

int16_zero_clear:
  push bp
  mov  bp, sp
  //SEG SS
  and  BYTE [bp + 0x06], #0xbf
  pop  bp
  iret

int16_zero_set:
  push bp
  mov  bp, sp
  //SEG SS
  or   BYTE [bp + 0x06], #0x40
  pop  bp
  iret

int16_F00:
  mov  bx, #0x0040
  mov  ds, bx

int16_wait_for_key:
  cli
  mov  bx, 0x001a
  cmp  bx, 0x001c
  jne  int16_key_found
  sti
  nop
#if 0
                           /* no key yet, call int 15h, function AX=9002 */
  0x50,                    /* push AX */
  0xb8, 0x02, 0x90,        /* mov AX, #0x9002 */
  0xcd, 0x15,              /* int 15h */
  0x58,                    /* pop  AX */
  0xeb, 0xea,              /* jmp   WAIT_FOR_KEY */
#endif
  jmp  int16_wait_for_key

int16_key_found:
  mov  bx, #0xf000
  mov  ds, bx
  call _int16_function
  popa
  popf
  pop  ds
#if 0
                           /* notify int16 complete w/ int 15h, function AX=9102 */
  0x50,                    /* push AX */
  0xb8, 0x02, 0x91,        /* mov AX, #0x9102 */
  0xcd, 0x15,              /* int 15h */
  0x58,                    /* pop  AX */
#endif
  iret



;-------------------------------------------------
;- INT09h : Keyboard Hardware Service Entry Point -
;-------------------------------------------------
.org 0xe987
int09_handler:
  cli
  push ax

  mov al, #0xAD      ;;disable keyboard
  out #0x64, al

  mov al, #0x0B
  out #0x20, al
  in  al, #0x20
  and al, #0x02
  jz  int09_finish

  in  al, #0x60             ;;read key from keyboard controller
  //test al, #0x80            ;;look for key release
  //jnz  int09_process_key    ;; dont pass releases to intercept?

  ;; check for extended key
  cmp  al, #0xe0
  jne int09_call_int15_4f
  
  push ds
  xor  ax, ax
  mov  ds, ax
  mov  al, BYTE [0x496]     ;; mf2_state |= 0x01
  or   al, #0x01
  mov  BYTE [0x496], al
  pop  ds
  
  in  al, #0x60             ;;read another key from keyboard controller

  sti

int09_call_int15_4f:
  push  ds
  pusha
#ifdef BX_CALL_INT15_4F
  mov  ah, #0x4f     ;; allow for keyboard intercept
  stc
  int  #0x15
  jnc  int09_done
#endif


//int09_process_key:
  mov   bx, #0xf000
  mov   ds, bx
  call  _int09_function

int09_done:
  popa
  pop   ds
  cli
  call eoi_master_pic

int09_finish:
  mov al, #0xAE      ;;enable keyboard
  out #0x64, al
  pop ax
  iret




;----------------------------------------
;- INT 13h Diskette Service Entry Point -
;----------------------------------------
.org 0xec59
int13_diskette:
  jmp int13_noeltorito

;---------------------------------------------
;- INT 0Eh Diskette Hardware ISR Entry Point -
;---------------------------------------------
.org 0xef57 ; INT 0Eh Diskette Hardware ISR Entry Point
int0e_handler:
  push ax
  push dx
  mov  dx, #0x03f4
  in   al, dx
  and  al, #0xc0
  cmp  al, #0xc0
  je   int0e_normal
  mov  dx, #0x03f5
  mov  al, #0x08 ; sense interrupt status
  out  dx, al
int0e_loop1:
  mov  dx, #0x03f4
  in   al, dx
  and  al, #0xc0
  cmp  al, #0xc0
  jne  int0e_loop1
int0e_loop2:
  mov  dx, #0x03f5
  in   al, dx
  mov  dx, #0x03f4
  in   al, dx
  and  al, #0xc0
  cmp  al, #0xc0
  je int0e_loop2
int0e_normal:
  push ds
  mov  ax, #0x0000 ;; segment 0000
  mov  ds, ax
  call eoi_master_pic
  mov  al, 0x043e
  or   al, #0x80 ;; diskette interrupt has occurred
  mov  0x043e, al
  pop  ds
  pop  dx
  pop  ax
  iret


.org 0xefc7 ; Diskette Controller Parameter Table
diskette_param_table:
;;  Since no provisions are made for multiple drive types, most
;;  values in this table are ignored.  I set parameters for 1.44M
;;  floppy here
db  0xAF
db  0x02 ;; head load time 0000001, DMA used
db  0x25
db  0x02
db    18
db  0x1B
db  0xFF
db  0x6C
db  0xF6
db  0x0F
db  0x08


;----------------------------------------
;- INT17h : Printer Service Entry Point -
;----------------------------------------
.org 0xefd2
int17_handler:
  push ds
  pusha
  mov  ax, #0x0000
  mov  ds, ax
  call _int17_function
  popa
  pop  ds
  iret

diskette_param_table2:
;;  New diskette parameter table adding 3 parameters from IBM
;;  Since no provisions are made for multiple drive types, most
;;  values in this table are ignored.  I set parameters for 1.44M
;;  floppy here
db  0xAF
db  0x02 ;; head load time 0000001, DMA used
db  0x25
db  0x02
db    18
db  0x1B
db  0xFF
db  0x6C
db  0xF6
db  0x0F
db  0x08
db    79 ;; maximum track
db     0 ;; data transfer rate
db     4 ;; drive type in cmos

.org 0xf045 ; INT 10 Functions 0-Fh Entry Point
  HALT(__LINE__)
  iret

;----------
;- INT10h -
;----------
.org 0xf065 ; INT 10h Video Support Service Entry Point
int10_handler:
  ;; dont do anything, since the VGA BIOS handles int10h requests
  iret

.org 0xf0a4 ; MDA/CGA Video Parameter Table (INT 1Dh)

;----------
;- INT12h -
;----------
.org 0xf841 ; INT 12h Memory Size Service Entry Point
; ??? different for Pentium (machine check)?
int12_handler:
  push ds
  mov  ax, #0x0040
  mov  ds, ax
  mov  ax, 0x0013
  pop  ds
  iret

;----------
;- INT11h -
;----------
.org 0xf84d ; INT 11h Equipment List Service Entry Point
int11_handler:
  push ds
  mov  ax, #0x0040
  mov  ds, ax
  mov  ax, 0x0010
  pop  ds
  iret

;----------
;- INT15h -
;----------
.org 0xf859 ; INT 15h System Services Entry Point
int15_handler:
  pushf
#if BX_APM
  cmp ah, #0x53
  je apm_call
#endif
  push  ds
  push  es
  cmp  ah, #0x86
  je int15_handler32
  cmp  ah, #0xE8
  je int15_handler32
  pusha
#if BX_USE_PS2_MOUSE
  cmp  ah, #0xC2
  je int15_handler_mouse
#endif
  call _int15_function
int15_handler_mouse_ret:
  popa
int15_handler32_ret:
  pop   es
  pop   ds
  popf
  jmp iret_modify_cf
#if BX_APM
apm_call:
  jmp _apmreal_entry
#endif

#if BX_USE_PS2_MOUSE
int15_handler_mouse:
  call _int15_function_mouse
  jmp int15_handler_mouse_ret
#endif

int15_handler32:
  pushad
  call _int15_function32
  popad
  jmp int15_handler32_ret

;; Protected mode IDT descriptor
;;
;; I just make the limit 0, so the machine will shutdown
;; if an exception occurs during protected mode memory
;; transfers.
;;
;; Set base to f0000 to correspond to beginning of BIOS,
;; in case I actually define an IDT later
;; Set limit to 0

pmode_IDT_info:
dw 0x0000  ;; limit 15:00
dw 0x0000  ;; base  15:00
db 0x0f    ;; base  23:16

;; Real mode IDT descriptor
;;
;; Set to typical real-mode values.
;; base  = 000000
;; limit =   03ff

rmode_IDT_info:
dw 0x03ff  ;; limit 15:00
dw 0x0000  ;; base  15:00
db 0x00    ;; base  23:16


;----------
;- INT1Ah -
;----------
.org 0xfe6e ; INT 1Ah Time-of-day Service Entry Point
int1a_handler:
#if BX_TCGBIOS
  cmp  ah, #0xbb
  jne  no_tcg
  pushf
  push ds
  push es
  pushad
  call _int1a_function32
  popad
  pop es
  pop ds
  popf
  iret
no_tcg:
#endif
#if BX_PCIBIOS
  cmp  ah, #0xb1
  jne  int1a_normal
  call pcibios_real
  jc   pcibios_error
  retf 2
pcibios_error:
  mov  bl, ah
  mov  ah, #0xb1
  push ds
  pusha
  mov ax, ss  ; set readable descriptor to ds, for calling pcibios
  mov ds, ax  ;  on 16bit protected mode.
  jmp int1a_callfunction
int1a_normal:
#endif
  push ds
  pusha
  xor  ax, ax
  mov  ds, ax
int1a_callfunction:
  call _int1a_function
  popa
  pop  ds
  iret

;;
;; int70h: IRQ8 - CMOS RTC
;;
int70_handler:
  push ds
  pusha
  xor  ax, ax
  mov  ds, ax
  call _int70_function
  popa
  pop  ds
  iret

;---------
;- INT08 -
;---------
.org 0xfea5 ; INT 08h System Timer ISR Entry Point
int08_handler:
  sti
  push eax
  push ds
  xor ax, ax
  mov ds, ax

  ;; time to turn off drive(s)?
  mov  al,0x0440
  or   al,al
  jz   int08_floppy_off
  dec  al
  mov  0x0440,al
  jnz  int08_floppy_off
  ;; turn motor(s) off
  push dx
  mov  dx,#0x03f2
  in   al,dx
  and  al,#0xcf
  out  dx,al
  pop  dx
int08_floppy_off:

  mov eax, 0x046c ;; get ticks dword
  inc eax

  ;; compare eax to one days worth of timer ticks at 18.2 hz
  cmp eax, #0x001800B0
  jb  int08_store_ticks
  ;; there has been a midnight rollover at this point
  xor eax, eax    ;; zero out counter
  inc BYTE 0x0470 ;; increment rollover flag

int08_store_ticks:
  mov 0x046c, eax ;; store new ticks dword
  ;; chain to user timer tick INT #0x1c
  //pushf
  //;; call_ep [ds:loc]
  //CALL_EP( 0x1c << 2 )
  int #0x1c
  cli
  call eoi_master_pic
  pop ds
  pop eax
  iret

.org 0xfef3 ; Initial Interrupt Vector Offsets Loaded by POST


.org 0xff00
.ascii "(c) 2002 MandrakeSoft S.A. Written by Kevin Lawton & the Bochs team."

;------------------------------------------------
;- IRET Instruction for Dummy Interrupt Handler -
;------------------------------------------------
.org 0xff53 ; IRET Instruction for Dummy Interrupt Handler
dummy_iret_handler:
  iret

.org 0xff54 ; INT 05h Print Screen Service Entry Point
  HALT(__LINE__)
  iret

#ifdef HVMTEST
.org 0xffe0
  jmp 0xf000:post;
#endif

.org 0xfff0 ; Power-up Entry Point
#ifdef HVMTEST
  jmp 0xd000:0x0003;
#else
  jmp 0xf000:post
#endif

.org 0xfff5 ; ASCII Date ROM was built - 8 characters in MM/DD/YY
.ascii BIOS_BUILD_DATE

.org 0xfffe ; System Model ID
db SYS_MODEL_ID
db 0x00   ; filler

.org 0xfa6e ;; Character Font for 320x200 & 640x200 Graphics (lower 128 characters)
ASM_END
/*
 * This font comes from the fntcol16.zip package (c) by  Joseph Gil 
 * found at ftp://ftp.simtel.net/pub/simtelnet/msdos/screen/fntcol16.zip
 * This font is public domain
 */ 
static Bit8u vgafont8[128*8]=
{
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x7e, 0x81, 0xa5, 0x81, 0xbd, 0x99, 0x81, 0x7e,
 0x7e, 0xff, 0xdb, 0xff, 0xc3, 0xe7, 0xff, 0x7e,
 0x6c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x10, 0x00,
 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x10, 0x00,
 0x38, 0x7c, 0x38, 0xfe, 0xfe, 0x7c, 0x38, 0x7c,
 0x10, 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x7c,
 0x00, 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00, 0x00,
 0xff, 0xff, 0xe7, 0xc3, 0xc3, 0xe7, 0xff, 0xff,
 0x00, 0x3c, 0x66, 0x42, 0x42, 0x66, 0x3c, 0x00,
 0xff, 0xc3, 0x99, 0xbd, 0xbd, 0x99, 0xc3, 0xff,
 0x0f, 0x07, 0x0f, 0x7d, 0xcc, 0xcc, 0xcc, 0x78,
 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18,
 0x3f, 0x33, 0x3f, 0x30, 0x30, 0x70, 0xf0, 0xe0,
 0x7f, 0x63, 0x7f, 0x63, 0x63, 0x67, 0xe6, 0xc0,
 0x99, 0x5a, 0x3c, 0xe7, 0xe7, 0x3c, 0x5a, 0x99,
 0x80, 0xe0, 0xf8, 0xfe, 0xf8, 0xe0, 0x80, 0x00,
 0x02, 0x0e, 0x3e, 0xfe, 0x3e, 0x0e, 0x02, 0x00,
 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x7e, 0x3c, 0x18,
 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x00,
 0x7f, 0xdb, 0xdb, 0x7b, 0x1b, 0x1b, 0x1b, 0x00,
 0x3e, 0x63, 0x38, 0x6c, 0x6c, 0x38, 0xcc, 0x78,
 0x00, 0x00, 0x00, 0x00, 0x7e, 0x7e, 0x7e, 0x00,
 0x18, 0x3c, 0x7e, 0x18, 0x7e, 0x3c, 0x18, 0xff,
 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x00,
 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0x00,
 0x00, 0x18, 0x0c, 0xfe, 0x0c, 0x18, 0x00, 0x00,
 0x00, 0x30, 0x60, 0xfe, 0x60, 0x30, 0x00, 0x00,
 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xfe, 0x00, 0x00,
 0x00, 0x24, 0x66, 0xff, 0x66, 0x24, 0x00, 0x00,
 0x00, 0x18, 0x3c, 0x7e, 0xff, 0xff, 0x00, 0x00,
 0x00, 0xff, 0xff, 0x7e, 0x3c, 0x18, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x30, 0x78, 0x78, 0x30, 0x30, 0x00, 0x30, 0x00,
 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x6c, 0x6c, 0xfe, 0x6c, 0xfe, 0x6c, 0x6c, 0x00,
 0x30, 0x7c, 0xc0, 0x78, 0x0c, 0xf8, 0x30, 0x00,
 0x00, 0xc6, 0xcc, 0x18, 0x30, 0x66, 0xc6, 0x00,
 0x38, 0x6c, 0x38, 0x76, 0xdc, 0xcc, 0x76, 0x00,
 0x60, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x18, 0x30, 0x60, 0x60, 0x60, 0x30, 0x18, 0x00,
 0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00,
 0x00, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x00, 0x00,
 0x00, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x60,
 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00,
 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x80, 0x00,
 0x7c, 0xc6, 0xce, 0xde, 0xf6, 0xe6, 0x7c, 0x00,
 0x30, 0x70, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x00,
 0x78, 0xcc, 0x0c, 0x38, 0x60, 0xcc, 0xfc, 0x00,
 0x78, 0xcc, 0x0c, 0x38, 0x0c, 0xcc, 0x78, 0x00,
 0x1c, 0x3c, 0x6c, 0xcc, 0xfe, 0x0c, 0x1e, 0x00,
 0xfc, 0xc0, 0xf8, 0x0c, 0x0c, 0xcc, 0x78, 0x00,
 0x38, 0x60, 0xc0, 0xf8, 0xcc, 0xcc, 0x78, 0x00,
 0xfc, 0xcc, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x00,
 0x78, 0xcc, 0xcc, 0x78, 0xcc, 0xcc, 0x78, 0x00,
 0x78, 0xcc, 0xcc, 0x7c, 0x0c, 0x18, 0x70, 0x00,
 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00,
 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x60,
 0x18, 0x30, 0x60, 0xc0, 0x60, 0x30, 0x18, 0x00,
 0x00, 0x00, 0xfc, 0x00, 0x00, 0xfc, 0x00, 0x00,
 0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0x00,
 0x78, 0xcc, 0x0c, 0x18, 0x30, 0x00, 0x30, 0x00,
 0x7c, 0xc6, 0xde, 0xde, 0xde, 0xc0, 0x78, 0x00,
 0x30, 0x78, 0xcc, 0xcc, 0xfc, 0xcc, 0xcc, 0x00,
 0xfc, 0x66, 0x66, 0x7c, 0x66, 0x66, 0xfc, 0x00,
 0x3c, 0x66, 0xc0, 0xc0, 0xc0, 0x66, 0x3c, 0x00,
 0xf8, 0x6c, 0x66, 0x66, 0x66, 0x6c, 0xf8, 0x00,
 0xfe, 0x62, 0x68, 0x78, 0x68, 0x62, 0xfe, 0x00,
 0xfe, 0x62, 0x68, 0x78, 0x68, 0x60, 0xf0, 0x00,
 0x3c, 0x66, 0xc0, 0xc0, 0xce, 0x66, 0x3e, 0x00,
 0xcc, 0xcc, 0xcc, 0xfc, 0xcc, 0xcc, 0xcc, 0x00,
 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00,
 0x1e, 0x0c, 0x0c, 0x0c, 0xcc, 0xcc, 0x78, 0x00,
 0xe6, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0xe6, 0x00,
 0xf0, 0x60, 0x60, 0x60, 0x62, 0x66, 0xfe, 0x00,
 0xc6, 0xee, 0xfe, 0xfe, 0xd6, 0xc6, 0xc6, 0x00,
 0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0x00,
 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x00,
 0xfc, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xf0, 0x00,
 0x78, 0xcc, 0xcc, 0xcc, 0xdc, 0x78, 0x1c, 0x00,
 0xfc, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0xe6, 0x00,
 0x78, 0xcc, 0xe0, 0x70, 0x1c, 0xcc, 0x78, 0x00,
 0xfc, 0xb4, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00,
 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xfc, 0x00,
 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0x00,
 0xc6, 0xc6, 0xc6, 0xd6, 0xfe, 0xee, 0xc6, 0x00,
 0xc6, 0xc6, 0x6c, 0x38, 0x38, 0x6c, 0xc6, 0x00,
 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0x30, 0x78, 0x00,
 0xfe, 0xc6, 0x8c, 0x18, 0x32, 0x66, 0xfe, 0x00,
 0x78, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x00,
 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x02, 0x00,
 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00,
 0x10, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00,
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
 0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x00, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0x76, 0x00,
 0xe0, 0x60, 0x60, 0x7c, 0x66, 0x66, 0xdc, 0x00,
 0x00, 0x00, 0x78, 0xcc, 0xc0, 0xcc, 0x78, 0x00,
 0x1c, 0x0c, 0x0c, 0x7c, 0xcc, 0xcc, 0x76, 0x00,
 0x00, 0x00, 0x78, 0xcc, 0xfc, 0xc0, 0x78, 0x00,
 0x38, 0x6c, 0x60, 0xf0, 0x60, 0x60, 0xf0, 0x00,
 0x00, 0x00, 0x76, 0xcc, 0xcc, 0x7c, 0x0c, 0xf8,
 0xe0, 0x60, 0x6c, 0x76, 0x66, 0x66, 0xe6, 0x00,
 0x30, 0x00, 0x70, 0x30, 0x30, 0x30, 0x78, 0x00,
 0x0c, 0x00, 0x0c, 0x0c, 0x0c, 0xcc, 0xcc, 0x78,
 0xe0, 0x60, 0x66, 0x6c, 0x78, 0x6c, 0xe6, 0x00,
 0x70, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00,
 0x00, 0x00, 0xcc, 0xfe, 0xfe, 0xd6, 0xc6, 0x00,
 0x00, 0x00, 0xf8, 0xcc, 0xcc, 0xcc, 0xcc, 0x00,
 0x00, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0x78, 0x00,
 0x00, 0x00, 0xdc, 0x66, 0x66, 0x7c, 0x60, 0xf0,
 0x00, 0x00, 0x76, 0xcc, 0xcc, 0x7c, 0x0c, 0x1e,
 0x00, 0x00, 0xdc, 0x76, 0x66, 0x60, 0xf0, 0x00,
 0x00, 0x00, 0x7c, 0xc0, 0x78, 0x0c, 0xf8, 0x00,
 0x10, 0x30, 0x7c, 0x30, 0x30, 0x34, 0x18, 0x00,
 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00,
 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0x00,
 0x00, 0x00, 0xc6, 0xd6, 0xfe, 0xfe, 0x6c, 0x00,
 0x00, 0x00, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0x00,
 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0x7c, 0x0c, 0xf8,
 0x00, 0x00, 0xfc, 0x98, 0x30, 0x64, 0xfc, 0x00,
 0x1c, 0x30, 0x30, 0xe0, 0x30, 0x30, 0x1c, 0x00,
 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00,
 0xe0, 0x30, 0x30, 0x1c, 0x30, 0x30, 0xe0, 0x00,
 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x00, 0x10, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0x00,
};

#ifdef HVMASSIST
ASM_START

// space for addresses in 32bit BIOS area; currently 256/4 entries
// are allocated
.org 0xcb00
jmptable:
db 0x5F, 0x5F, 0x5F, 0x4A, 0x4D, 0x50, 0x54 ;; ___JMPT
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;;  64 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 128 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 192 bytes

//
// MP Tables
// just carve out some blank space for HVMLOADER to write the MP tables to
//
// NOTE: There should be enough space for a 32 processor entry MP table
//
.org 0xcc00
db 0x5F, 0x5F, 0x5F, 0x48, 0x56, 0x4D, 0x4D, 0x50 ;; ___HVMMP
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;;  64 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 128 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 192 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 256 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 320 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 384 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 448 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 512 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 576 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 640 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 704 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 768 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 832 bytes
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;; 896 bytes

.align 16
smbios_entry_point:
db 0,0,0,0,0,0,0,0 ; 8 bytes
db 0,0,0,0,0,0,0,0 ; 16 bytes
db 0,0,0,0,0,0,0,0 ; 24 bytes
db 0,0,0,0,0,0,0   ; 31 bytes
ASM_END

#else // !HVMASSIST

ASM_START
.org 0xcc00
// bcc-generated data will be placed here

// For documentation of this config structure, look on developer.intel.com and
// search for multiprocessor specification.  Note that when you change anything
// you must update the checksum (a pain!).  It would be better to construct this
// with C structures, or at least fill in the checksum automatically.
//
// Maybe this structs could be moved elsewhere than d000

#if (BX_SMP_PROCESSORS==1)
  // no structure necessary.
#elif (BX_SMP_PROCESSORS==2)
// define the Intel MP Configuration Structure for 2 processors at
// APIC ID 0,1.  I/O APIC at ID=2.
.align 16
mp_config_table:
  db 0x50, 0x43, 0x4d, 0x50  ;; "PCMP" signature
  dw (mp_config_end-mp_config_table)  ;; table length
  db 4 ;; spec rev
  db 0x65 ;; checksum
  .ascii "BOCHSCPU"     ;; OEM id = "BOCHSCPU"
  db 0x30, 0x2e, 0x31, 0x20 ;; vendor id = "0.1         "
  db 0x20, 0x20, 0x20, 0x20 
  db 0x20, 0x20, 0x20, 0x20
  dw 0,0 ;; oem table ptr
  dw 0 ;; oem table size
  dw 20 ;; entry count
  dw 0x0000, 0xfee0 ;; memory mapped address of local APIC
  dw 0 ;; extended table length
  db 0 ;; extended table checksum
  db 0 ;; reserved
mp_config_proc0:
  db 0 ;; entry type=processor
  db 0 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 3 ;; cpu flags: enabled, bootstrap processor
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_proc1:
  db 0 ;; entry type=processor
  db 1 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 1 ;; cpu flags: enabled
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_isa_bus:
  db 1 ;; entry type=bus
  db 0 ;; bus ID
  db 0x49, 0x53, 0x41, 0x20, 0x20, 0x20  ;; bus type="ISA   "
mp_config_ioapic:
  db 2 ;; entry type=I/O APIC
  db 2 ;; apic id=2. linux will set.
  db 0x11 ;; I/O APIC version number
  db 1 ;; flags=1=enabled
  dw 0x0000, 0xfec0 ;; memory mapped address of I/O APIC
mp_config_irqs:
  db 3 ;; entry type=I/O interrupt
  db 0 ;; interrupt type=vectored interrupt
  db 0,0 ;; flags po=0, el=0 (linux uses as default)
  db 0 ;; source bus ID is ISA
  db 0 ;; source bus IRQ
  db 2 ;; destination I/O APIC ID
  db 0 ;; destination I/O APIC interrrupt in
  ;; repeat pattern for interrupts 0-15
  db 3,0,0,0,0,1,2,1
  db 3,0,0,0,0,2,2,2
  db 3,0,0,0,0,3,2,3
  db 3,0,0,0,0,4,2,4
  db 3,0,0,0,0,5,2,5
  db 3,0,0,0,0,6,2,6
  db 3,0,0,0,0,7,2,7
  db 3,0,0,0,0,8,2,8
  db 3,0,0,0,0,9,2,9
  db 3,0,0,0,0,10,2,10
  db 3,0,0,0,0,11,2,11
  db 3,0,0,0,0,12,2,12
  db 3,0,0,0,0,13,2,13
  db 3,0,0,0,0,14,2,14
  db 3,0,0,0,0,15,2,15
#elif (BX_SMP_PROCESSORS==4)
// define the Intel MP Configuration Structure for 4 processors at
// APIC ID 0,1,2,3.  I/O APIC at ID=4.
.align 16
mp_config_table:
  db 0x50, 0x43, 0x4d, 0x50  ;; "PCMP" signature
  dw (mp_config_end-mp_config_table)  ;; table length
  db 4 ;; spec rev
  db 0xdd ;; checksum
  .ascii "BOCHSCPU"     ;; OEM id = "BOCHSCPU"
  db 0x30, 0x2e, 0x31, 0x20 ;; vendor id = "0.1         "
  db 0x20, 0x20, 0x20, 0x20 
  db 0x20, 0x20, 0x20, 0x20
  dw 0,0 ;; oem table ptr
  dw 0 ;; oem table size
  dw 22 ;; entry count
  dw 0x0000, 0xfee0 ;; memory mapped address of local APIC
  dw 0 ;; extended table length
  db 0 ;; extended table checksum
  db 0 ;; reserved
mp_config_proc0:
  db 0 ;; entry type=processor
  db 0 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 3 ;; cpu flags: enabled, bootstrap processor
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_proc1:
  db 0 ;; entry type=processor
  db 1 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 1 ;; cpu flags: enabled
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_proc2:
  db 0 ;; entry type=processor
  db 2 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 1 ;; cpu flags: enabled
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_proc3:
  db 0 ;; entry type=processor
  db 3 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 1 ;; cpu flags: enabled
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_isa_bus:
  db 1 ;; entry type=bus
  db 0 ;; bus ID
  db 0x49, 0x53, 0x41, 0x20, 0x20, 0x20  ;; bus type="ISA   "
mp_config_ioapic:
  db 2 ;; entry type=I/O APIC
  db 4 ;; apic id=4. linux will set.
  db 0x11 ;; I/O APIC version number
  db 1 ;; flags=1=enabled
  dw 0x0000, 0xfec0 ;; memory mapped address of I/O APIC
mp_config_irqs:
  db 3 ;; entry type=I/O interrupt
  db 0 ;; interrupt type=vectored interrupt
  db 0,0 ;; flags po=0, el=0 (linux uses as default)
  db 0 ;; source bus ID is ISA
  db 0 ;; source bus IRQ
  db 4 ;; destination I/O APIC ID
  db 0 ;; destination I/O APIC interrrupt in
  ;; repeat pattern for interrupts 0-15
  db 3,0,0,0,0,1,4,1
  db 3,0,0,0,0,2,4,2
  db 3,0,0,0,0,3,4,3
  db 3,0,0,0,0,4,4,4
  db 3,0,0,0,0,5,4,5
  db 3,0,0,0,0,6,4,6
  db 3,0,0,0,0,7,4,7
  db 3,0,0,0,0,8,4,8
  db 3,0,0,0,0,9,4,9
  db 3,0,0,0,0,10,4,10
  db 3,0,0,0,0,11,4,11
  db 3,0,0,0,0,12,4,12
  db 3,0,0,0,0,13,4,13
  db 3,0,0,0,0,14,4,14
  db 3,0,0,0,0,15,4,15
#elif (BX_SMP_PROCESSORS==8)
// define the Intel MP Configuration Structure for 8 processors at
// APIC ID 0,1,2,3,4,5,6,7.  I/O APIC at ID=8.
.align 16
mp_config_table:
  db 0x50, 0x43, 0x4d, 0x50  ;; "PCMP" signature
  dw (mp_config_end-mp_config_table)  ;; table length
  db 4 ;; spec rev
  db 0xc3 ;; checksum
  .ascii "BOCHSCPU"     ;; OEM id = "BOCHSCPU"
  db 0x30, 0x2e, 0x31, 0x20 ;; vendor id = "0.1         "
  db 0x20, 0x20, 0x20, 0x20 
  db 0x20, 0x20, 0x20, 0x20
  dw 0,0 ;; oem table ptr
  dw 0 ;; oem table size
  dw 26 ;; entry count
  dw 0x0000, 0xfee0 ;; memory mapped address of local APIC
  dw 0 ;; extended table length
  db 0 ;; extended table checksum
  db 0 ;; reserved
mp_config_proc0:
  db 0 ;; entry type=processor
  db 0 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 3 ;; cpu flags: enabled, bootstrap processor
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_proc1:
  db 0 ;; entry type=processor
  db 1 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 1 ;; cpu flags: enabled
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_proc2:
  db 0 ;; entry type=processor
  db 2 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 1 ;; cpu flags: enabled
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_proc3:
  db 0 ;; entry type=processor
  db 3 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 1 ;; cpu flags: enabled
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_proc4:
  db 0 ;; entry type=processor
  db 4 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 1 ;; cpu flags: enabled
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_proc5:
  db 0 ;; entry type=processor
  db 5 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 1 ;; cpu flags: enabled
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_proc6:
  db 0 ;; entry type=processor
  db 6 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 1 ;; cpu flags: enabled
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_proc7:
  db 0 ;; entry type=processor
  db 7 ;; local APIC id
  db 0x11 ;; local APIC version number
  db 1 ;; cpu flags: enabled
  db 0,6,0,0 ;; cpu signature
  dw 0x201,0 ;; feature flags
  dw 0,0 ;; reserved
  dw 0,0 ;; reserved
mp_config_isa_bus:
  db 1 ;; entry type=bus
  db 0 ;; bus ID
  db 0x49, 0x53, 0x41, 0x20, 0x20, 0x20  ;; bus type="ISA   "
mp_config_ioapic:
  db 2 ;; entry type=I/O APIC
  db 8 ;; apic id=8
  db 0x11 ;; I/O APIC version number
  db 1 ;; flags=1=enabled
  dw 0x0000, 0xfec0 ;; memory mapped address of I/O APIC
mp_config_irqs:
  db 3 ;; entry type=I/O interrupt
  db 0 ;; interrupt type=vectored interrupt
  db 0,0 ;; flags po=0, el=0 (linux uses as default)
  db 0 ;; source bus ID is ISA
  db 0 ;; source bus IRQ
  db 8 ;; destination I/O APIC ID
  db 0 ;; destination I/O APIC interrrupt in
  ;; repeat pattern for interrupts 0-15
  db 3,0,0,0,0,1,8,1
  db 3,0,0,0,0,2,8,2
  db 3,0,0,0,0,3,8,3
  db 3,0,0,0,0,4,8,4
  db 3,0,0,0,0,5,8,5
  db 3,0,0,0,0,6,8,6
  db 3,0,0,0,0,7,8,7
  db 3,0,0,0,0,8,8,8
  db 3,0,0,0,0,9,8,9
  db 3,0,0,0,0,10,8,10
  db 3,0,0,0,0,11,8,11
  db 3,0,0,0,0,12,8,12
  db 3,0,0,0,0,13,8,13
  db 3,0,0,0,0,14,8,14
  db 3,0,0,0,0,15,8,15
#else
#  error Sorry, rombios only has configurations for 1, 2, 4 or 8 processors.
#endif  // if (BX_SMP_PROCESSORS==...)

mp_config_end:   // this label used to find length of mp structure
 db 0

#if (BX_SMP_PROCESSORS>1)
.align 16
mp_floating_pointer_structure:
db 0x5f, 0x4d, 0x50, 0x5f   ; "_MP_" signature
dw mp_config_table, 0xf ;; pointer to MP configuration table
db 1     ;; length of this struct in 16-bit byte chunks
db 4     ;; MP spec revision
db 0xc1  ;; checksum
db 0     ;; MP feature byte 1.  value 0 means look at the config table
db 0,0,0,0     ;; MP feature bytes 2-5.
#endif

ASM_END

#endif // HVMASSIST
