'******************************************************************************
'* 
'* SCRIPT PC BIOS (Generic x86 BIOS emulation)
'*
'* Version history:
'*  2007,2008 - initial code (by WadiM)
'*
'* Note: - Generic BIOS implementation (to use with emulator's own x86 models)
'*       - Based on info from books on x86 and Ralf Brown's Interrupt List
'*       - Most of values must occupy exact places for real BIOS compatibility
'*       - If interrupt handler can change flags, use RETF 2h instead of IRET
'*       - In future inlined ASM->BYTES translator will be added for writing
'*         native machine-coded interrupt handlers (for more "real" BIOS).
'*       - EINT byte (0xF1 0xbyte) is call to emulated interrupt handler.
'*       - external video BIOS supported, if it placed at 0xC000:0 (VGA etc.)
'*
'******************************************************************************

//interrupt processing modules
use module "eint_08h" : use module "eint_09h"  
use module "cga\eint_10h" : use module "vga\eint_10h" 
use module "eint_11h" : use module "eint_12h"
use module "eint_13h" : use module "eint_14h"
use module "eint_15h" : use module "eint_16h" 
use module "eint_19h" : use module "eint_1Ah" 
use module "bios_vid"

//constants
const ROM_SEG as integer = 0xF000 'base segment of ROM BIOS
const ROM_BASE as integer = shl(ROM_SEG,4)  'base address of ROM BIOS

//variables
dim i as integer, j as integer, s as string

public procedure GenerateBIOS(video as string="cga")

? "[BIOS] Emulated BIOS generation... ";

'/////////////////////////////// INIT DEVICES /////////////////////////////////

'init x86 CPU 
cpu.SS=0 : cpu.ESP=0x3FD 'pointer to initial stack
cpu.FLAGS.IF=false 'disable interrupts (no need them on startup)

'init PICs (master and slave)
pc.WritePort(0x20,0x13) : pc.WritePort(0x21,8) : pc.WritePort(0x21,9) 'int08h+
pc.WritePort(0xA0,0x13) : pc.WritePort(0xA1,0x70) : pc.WritePort(0xA1,9)'int70h+

'init PIT
pc.WritePort(0x43,0x36) : pc.WritePort(0x40,0) : pc.WritePort(0x40,0)
pc.WritePort(0x43,0x76) : pc.WritePort(0x41,0) : pc.WritePort(0x41,0)
pc.WritePort(0x43,182)  : pc.WritePort(0x42,51) : pc.WritePort(0x42,1)

'init KBC (for AT only, XT ignore it)
pc.WritePort(0x64,0x60) : pc.WritePort(0x60,0x45) 'IRQ, Keyb, XTCodes
pc.WritePort(0x64,0xD1) : pc.WritePort(0x60,0xDF) 'A20=On

'///////////////////////////// GENERAL INIT ///////////////////////////////////

'Starting point at (ROM_SEG:FFF0h) (JMP far ROM_SEG:E05Bh = to POST)
mem.Pos=ROM_BASE+0xFFF0 : mem.Byte=0xEA : mem.Dword=shl(ROM_SEG,16) or 0xE05B

'Platform identification byte at (ROM_SEG:FFFEh)
mem.Byte(ROM_BASE+0xFFFE)=iif(ucase(cpu.Name)="I8086",0xFB,0xFC)

'BIOS release date (ROM_SEG:FFF5h..FFFCh)
mem.Pos=ROM_BASE+0xFFF5 : s="05/01/09" 
for i=1 to Length(s) : mem.Byte=Asc(Mid(s,i,1)) : next

'Empty interrupt handler at (ROM_SEG:FF53h)
mem.Byte(ROM_BASE+0xFF53)=0xCF 'IRET

'Fill interrupt table with pointer to empty handler (ROM_SEG:FF53h)
mem.Pos=0 : for i=0 to 255 : mem.Dword=shl(ROM_SEG,16) or 0xFF53 : next

'///////////////////////////// DATA AREA INIT /////////////////////////////////

Exec("bios_dat") 'Call to external script 

'//////////////////////////// VIDEO BIOS INIT /////////////////////////////////
                               
InitVideoBIOS(video) 'video BIOS initialization

'///////////////////// INT 05H HANDLER (PRINT SCREEN) /////////////////////////

'(ROM_SEG:FF54h..FFEFh) - not implemented yet (IRET as temporary stub)
'invoked by INT 09H handler when PrtSc key is pressed (or directly by apps)
mem.Dword(0x05*4)=shl(ROM_SEG,16) or 0xFF54       'interrupt table vector 05h
mem.Pos=ROM_BASE+0xFF54                           'handler starting address
mem.Bytes=Array(0xCF)                             'IRET

'////////////////// INT 08H HANDLER (HARDWARE TIMER IRQ0) /////////////////////

'(ROM_SEG:FEA5h..FEF2h) - call emulated INT 08h, real INT 1Ch and inform PIC
pc.CallEmuInt(0x08)=EINT_08H  'emulated interrupt
mem.Dword(0x08*4)=shl(ROM_SEG,16) or 0xFEA5       'interrupt table vector 08h
mem.Pos=ROM_BASE+0xFEA5                           'handler starting address
mem.Bytes=Array(0xF1,0x08)                        'EINT 08h
mem.Bytes=Array(0x50,0x51,0x52,0x53,0x55)         'PUSH AX,CX,DX,BX,BP
mem.Bytes=Array(0x56,0x57,0x06,0x1E)              'PUSH SI,DI,ES,DS

/* //inlined and simplified int 08h handling - but speed advantage is small
mem.Bytes=Array(0xB8,0x40,0x00)                   'MOV AX,40h
mem.Bytes=Array(0x8E,0xD8)                        'MOV DS,AX
mem.Bytes=Array(0xA1,0x6C,0x00)                   'MOV AX,DS:[6Ch]
mem.Bytes=Array(0x40)                             'INC AX
mem.Bytes=Array(0xA3,0x6C,0x00)                   'MOV DS:[6Ch],AX
mem.Bytes=Array(0x75,0x07)                        'JNZ NO_INC_HI_PART
mem.Bytes=Array(0xA1,0x6E,0x00)                   'MOV AX,DS:[6Eh]
mem.Bytes=Array(0x40)                             'INC AX
mem.Bytes=Array(0xA3,0x6E,0x00)                   'MOV DS:[6CE],AX
*/

mem.Bytes=Array(0xCD,0x1C)                        'INT 1Ch
mem.Bytes=Array(0xB0,0x20)                        'MOV AL,20H
mem.Bytes=Array(0xE6,0x20)                        'OUT 20H,AL
mem.Bytes=Array(0x1F,0x07,0x5F,0x5E)              'POP DS,ES,DI,SI
mem.Bytes=Array(0x5D,0x5B,0x5A,0x59,0x58)         'POP BP,BX,DX,CX,AX
mem.Bytes=Array(0xCF)                             'IRET

'////////////////// INT 09H HANDLER (HARDWARE KEYBOARD IRQ1) //////////////////

'(ROM_SEG:E987h..EC58h) - call emulated INT 09h and inform PIC
pc.CallEmuInt(0x09)=EINT_09H  'emulated interrupt
mem.Dword(0x09*4)=shl(ROM_SEG,16) or 0xE987       'interrupt table vector 09h
mem.Pos=ROM_BASE+0xE987                           'handler starting address
mem.Bytes=Array(0x50)                             'PUSH AX
mem.Bytes=Array(0x51)                             'PUSH CX
mem.Bytes=Array(0x52)                             'PUSH DX
mem.Bytes=Array(0x53)                             'PUSH BX
mem.Bytes=Array(0x55)                             'PUSH BP
mem.Bytes=Array(0x56)                             'PUSH SI
mem.Bytes=Array(0x57)                             'PUSH DI
mem.Bytes=Array(0x1E)                             'PUSH DS
mem.Bytes=Array(0x06)                             'PUSH ES
mem.Bytes=Array(0xF9)                             'STC
mem.Bytes=Array(0xB4,0x4F)                        'MOV AH,4FH 'func. index
mem.Bytes=Array(0xE4,0x60)                        'IN AL,60H 'read key
//TODO - temp .disabled because strange answers from some DOS-es (0x0E, keyups)
//mem.Bytes=Array(0xCD,0x15)                        'INT 15H 'external processing
mem.Bytes=Array(0x73,0x02)                        'JNC +2 'already processed
mem.Bytes=Array(0xF1,0x09)                        'EINT 09h 'emulated int
mem.Bytes=Array(0xB0,0x20)                        'MOV AL,20H 'inform pic
mem.Bytes=Array(0xE6,0x20)                        'OUT 20H,AL 
mem.Bytes=Array(0x07)                             'POP ES
mem.Bytes=Array(0x1F)                             'POP DS
mem.Bytes=Array(0x5F)                             'POP DI
mem.Bytes=Array(0x5E)                             'POP SI
mem.Bytes=Array(0x5D)                             'POP BP
mem.Bytes=Array(0x5B)                             'POP BX
mem.Bytes=Array(0x5A)                             'POP DX
mem.Bytes=Array(0x59)                             'POP CX
mem.Bytes=Array(0x58)                             'POP AX
mem.Bytes=Array(0xCF)                             'IRET

'//////////////////// INT 0EH HANDLER (FDD CONTROLLER IRQ6) ///////////////////

'(ROM_SEG:EF57h..EFC6h) - not implemented yet (IRET as temporary stub)
'invoked by FDC when some operation was finished

mem.Dword(0x0C*4)=shl(ROM_SEG,16) or 0xEF57       'vector 0Ch - COM1 //TODO
mem.Dword(0x0B*4)=shl(ROM_SEG,16) or 0xEF57       'vector 0Bh - COM2 //TODO

mem.Dword(0x0E*4)=shl(ROM_SEG,16) or 0xEF57       'interrupt table vector 0Eh
mem.Pos=ROM_BASE+0xEF57                           'handler starting address
mem.Bytes=Array(0x50)                             'PUSH AX
mem.Bytes=Array(0xB0,0x20)                        'MOV AL,20H
mem.Bytes=Array(0xE6,0x20)                        'OUT 20H,AL
mem.Bytes=Array(0x58)                             'POP AX
mem.Bytes=Array(0xCF)                             'IRET

'//////////////////// INT 10H HANDLER (VIDEO BIOS SERVICES) ///////////////////

'(ROM_SEG:F065h..F0A3h) - call emulated INT 10h
select case UCase(video) 'emulated interrupt
 case "VGA": pc.CallEmuInt(0x10)=EINT_10H_VGA 'VGA
 case else:  pc.CallEmuInt(0x10)=EINT_10H_CGA 'CGA
end select
mem.Dword(0x10*4)=shl(ROM_SEG,16) or 0xF065       'interrupt table vector 10h
mem.Pos=ROM_BASE+0xF065                           'handler starting address
mem.Bytes=Array(0xF1,0x10)                        'EINT 10h
mem.Bytes=Array(0xFB)                             'STI
mem.Bytes=Array(0xCA,0x02,0x00)                   'RETF 0002h

'/////////////////// INT 11H HANDLER (GET EQUIPMENT LIST) /////////////////////

'(ROM_SEG:F84Dh..F858h) - call emulated INT 11h
pc.CallEmuInt(0x11)=EINT_11H  'emulated interrupt
mem.Dword(0x11*4)=shl(ROM_SEG,16) or 0xF84D       'interrupt table vector 11h
mem.Pos=ROM_BASE+0xF84D                           'handler starting address
mem.Bytes=Array(0xF1,0x11)                        'EINT 11h
mem.Bytes=Array(0xCF)                             'IRET

'////////////////// INT 12H HANDLER (GET BASE MEMORY SIZE) ////////////////////

'(ROM_SEG:F841h..F84Ch) - call emulated INT 12h
pc.CallEmuInt(0x12)=EINT_12H  'emulated interrupt
mem.Dword(0x12*4)=shl(ROM_SEG,16) or 0xF841       'interrupt table vector 12h
mem.Pos=ROM_BASE+0xF841                           'handler starting address
mem.Bytes=Array(0xF1,0x12)                        'EINT 12h
mem.Bytes=Array(0xCF)                             'IRET

'////////////////// INT 13H/40H HANDLER (FDD/HDD SERVICES) ////////////////////

'(ROM_SEG:EC59h..EF56h) - call emulated INT 13h
pc.CallEmuInt(0x13)=EINT_13H  'emulated interrupt
mem.Dword(0x13*4)=shl(ROM_SEG,16) or 0xEC59       'interrupt table vector 13h
mem.Dword(0x40*4)=shl(ROM_SEG,16) or 0xEC59       'interrupt table vector 40h
mem.Pos=ROM_BASE+0xEC59                           'handler starting address
mem.Bytes=Array(0xF1,0x13)                        'EINT 13h
mem.Bytes=Array(0xFB)                             'STI
mem.Bytes=Array(0xCA,0x02,0x00)                   'RETF 0002h

'////////////////// INT 14H HANDLER (SERIAL PORT SERVICES) ////////////////////

'(ROM_SEG:E739h..E82Dh) - call emulated INT 14h
pc.CallEmuInt(0x14)=EINT_14H  'emulated interrupt
mem.Dword(0x14*4)=shl(ROM_SEG,16) or 0xE739       'interrupt table vector 14h
mem.Pos=ROM_BASE+0xE739                           'handler starting address
mem.Bytes=Array(0xF1,0x14)                        'EINT 14h
mem.Bytes=Array(0xFB)                             'STI
mem.Bytes=Array(0xCA,0x02,0x00)                   'RETF 0002h

'////////////////// INT 15H HANDLER (BIOS SYSTEM SERVICES) ////////////////////

'(ROM_SEG:F859h..FA6Dh) - call emulated INT 15h
pc.CallEmuInt(0x15)=EINT_15H  'emulated interrupt
mem.Dword(0x15*4)=shl(ROM_SEG,16) or 0xF859       'interrupt table vector 15h
mem.Pos=ROM_BASE+0xF859                           'handler starting address
mem.Bytes=Array(0xF1,0x15)                        'EINT 15h
mem.Bytes=Array(0xFB)                             'STI
mem.Bytes=Array(0xCA,0x02,0x00)                   'RETF 0002h

'/////////////////// INT 16H HANDLER (KEYBOARD SERVICES) //////////////////////

'(ROM_SEG:E82Eh..E986h) - call emulated INT 16h
pc.CallEmuInt(0x16)=EINT_16H  'emulated interrupt
mem.Dword(0x16*4)=shl(ROM_SEG,16) or 0xE82E       'interrupt table vector 16h
mem.Pos=ROM_BASE+0xE82E                           'handler starting address
mem.Bytes=Array(0x80,0xFC,0x00)                   'CMP AH,00h
mem.Bytes=Array(0x75,0x13)                        'JNZ @B
mem.Bytes=Array(0xFB)                             'STI
mem.Bytes=Array(0x50)                             'PUSH AX   
mem.Bytes=Array(0x1E)                             'PUSH DS
mem.Bytes=Array(0xB8,0x40,0x00)                   'MOV AX,40h
mem.Bytes=Array(0x8E,0xD8)                        'MOV DS,AX
mem.Bytes=Array(0xA1,0x1A,0x00)                   '@A: MOV AX,DS:[1Ah]
mem.Bytes=Array(0x3B,0x06,0x1C,0x00)              'CMP AX,DS:[1Ch]
mem.Bytes=Array(0x74,0xF7)                        'JZ @A
mem.Bytes=Array(0x1F)                             'POP DS
mem.Bytes=Array(0x58)                             'POP AX
mem.Bytes=Array(0xF1,0x16)                        '@B: EINT 16h
mem.Bytes=Array(0xFB)                             'STI
mem.Bytes=Array(0xCA,0x02,0x00)                   'RETF 0002h

'/////////////////// INT 17H HANDLER (PRINTER SERVICES) ///////////////////////

'(ROM_SEG:EFD2h..F044h) - not implemented yet
mem.Dword(0x17*4)=shl(ROM_SEG,16) or 0xEFD2       'interrupt table vector 17h
mem.Pos=ROM_BASE+0xEFD2                           'handler starting address
mem.Bytes=Array(0xFB)                             'STI
mem.Bytes=Array(0xCA,0x02,0x00)                   'RETF 0002h

'//////////////////// INT 19H HANDLER (BOOTSTRAP LOADER) //////////////////////

'(ROM_SEG:E6F2h..E728h) - call emulated INT 19h
pc.CallEmuInt(0x19)=EINT_19H  'emulated interrupt
mem.Dword(0x19*4)=shl(ROM_SEG,16) or 0xE6F2       'interrupt table vector 19h
mem.Pos=ROM_BASE+0xE6F2                           'handler starting address
mem.Bytes=Array(0xF1,0x19)                        'EINT 19h
mem.Bytes=Array(0xCF)                             'IRET

'////////////////////// INT 1AH HANDLER (CLOCK SERVICES) //////////////////////

'(ROM_SEG:FE6Eh..FEA4h) - call emulated INT 1Ah and RETF to return flags
pc.CallEmuInt(0x1A)=EINT_1AH  'emulated interrupt
mem.Dword(0x1A*4)=shl(ROM_SEG,16) or 0xFE6E       'interrupt table vector 1Ah
mem.Pos=ROM_BASE+0xFE6E                           'handler starting address
mem.Bytes=Array(0xF1,0x1A)                        'EINT 1Ah
mem.Bytes=Array(0xFB)                             'STI
mem.Bytes=Array(0xCA,0x02,0x00)                   'RETF 0002h

'//////////////////////// INT 70H HANDLER (RTC IRQ8) //////////////////////////

'(ROM_SEG:FE90h..FEA4h) - take part of INT 1A memory space
mem.Dword(0x70*4)=shl(ROM_SEG,16) or 0xFE90       'interrupt table vector 70h
mem.Pos=ROM_BASE+0xFE90                           'handler starting address
mem.Bytes=Array(0x50)                             'PUSH AX
mem.Bytes=Array(0xB0,0x0C)                        'MOV AL,0CH
mem.Bytes=Array(0xE6,0x70)                        'OUT 70H,AL 'RTC EOI
mem.Bytes=Array(0xE4,0x71)                        'IN AL,71H  'by read cell C
mem.Bytes=Array(0xB0,0x20)                        'MOV AL,20H
mem.Bytes=Array(0xE6,0xA0)                        'OUT A0H,AL 'pic-2 EOI
mem.Bytes=Array(0xE6,0x20)                        'OUT 20H,AL 'pic-1 EOI
mem.Bytes=Array(0x58)                             'POP AX
mem.Bytes=Array(0xCF)                             'IRET

'////////////////////// INITIAL TESTS HANDLER (POST) //////////////////////////

'(ROM_SEG:E05Bh..E2C2h) - call INT 19H to boot from disk (temporary stub)
mem.Pos=ROM_BASE+0xE05B                           'handler starting address
mem.Bytes=Array(0x9A,0x03,0x00,0x00,0xC0)         'CALL C000:0003 'ext. BIOS
mem.Bytes=Array(0xCD,0x19)                        'INT 19h

'///////////////////// NON-MASKABLE INT HANDLER (NMI) /////////////////////////

'(ROM_SEG:E2C3h..E400h) - call INT 19H to boot from disk (temporary stub)
mem.Pos=ROM_BASE+0xE2C3                           'handler starting address
mem.Bytes=Array(0xCD,0x19)                        'INT 19h

'//////////////////////// FDD PARAMS TABLE (INT 1EH) //////////////////////////

'(ROM_SEG:EFC7h..EFD1h) - params of FDD (1.44 Mb 3.5" by default)
mem.Dword(0x1E*4)=shl(ROM_SEG,16) or 0xEFC7       'interrupt table vector 1Eh
mem.Pos=ROM_BASE+0xEFC7                           'table starting address
mem.Byte=0xAF                                     'head params
mem.Byte=0x02                                     'head and DMA (<>3 for KQ1)
mem.Byte=37                                       'ticks to turn off motor
mem.Byte=2                                        '=512 bytes in sector
mem.Byte=18                                       'sectors per track
mem.Byte=0x1B                                     'gap between sectors
mem.Byte=0                                        'ignored (data length)
mem.Byte=0x6C                                     'gap when formatting
mem.Byte=0xF6                                     'format filler
mem.Byte=0x0F                                     'head positioning time (ms)
mem.Byte=0x08                                     'motor start time

////////////////////// HDD PARAMS TABLE (INT 41h, 46h) ////////////////////////

'(ROM_SEG:E401h..E6F1h) - params of HDDs (SPC BIOS used types 0,1 only)
//Type 0 (first HDD)
mem.Dword(0x41*4)=shl(ROM_SEG,16) or 0xE401       'interrupt table vector 41h
if not(emuDrives.HDD(0).Detected) then            'detect hard disk format
   DetectHardDisk(emuDrives.HDD(0)) : end if
mem.Pos=ROM_BASE+0xE401                           'Type 0 starting address
mem.Word=iif(emuDrives.HDD(0).Detected,emuDrives.HDD(0).CylinderCount,0)
                                                  'cylinders/tracks count
mem.Byte=iif(emuDrives.HDD(0).Detected,emuDrives.HDD(0).HeadCount,0) 
                                                  'heads/sides count
mem.Dword=0                                       'ignorable
mem.Byte=0                                        'ignorable
mem.Byte=0                                        'bit 3 set if heads>8???
mem.Word=0                                        'ignorable
mem.Byte=0                                        'ignorable
mem.Word=0                                        'ignorable
mem.Byte=iif(emuDrives.HDD(0).Detected,emuDrives.HDD(0).SectorCount,0) 
                                                  'sectors on track 
mem.Byte=0                                        'reserved
//Type 1 (second HDD)
mem.Dword(0x46*4)=shl(ROM_SEG,16) or 0xE401+16    'interrupt table vector 46h
if not(emuDrives.HDD(1).Detected) then            'detect hard disk format
   DetectHardDisk(emuDrives.HDD(1)) : end if
mem.Pos=ROM_BASE+0xE401+16                        'Type 1 starting address
mem.Word=iif(emuDrives.HDD(1).Detected,emuDrives.HDD(1).CylinderCount,0)
                                                  'cylinders/tracks count
mem.Byte=iif(emuDrives.HDD(1).Detected,emuDrives.HDD(1).HeadCount,0)  
                                                  'heads/sides count
mem.Dword=0                                       'ignorable
mem.Byte=0                                        'ignorable
mem.Byte=0                                        'bit 3 set if heads>8???
mem.Word=0                                        'ignorable
mem.Byte=0                                        'ignorable
mem.Word=0                                        'ignorable
mem.Byte=iif(emuDrives.HDD(1).Detected,emuDrives.HDD(1).SectorCount,0) 
                                                  'sectors on track 
mem.Byte=0                                        'reserved

'//////////////////////// VIDEO INTERFACE TABLE ///////////////////////////////

'(ROM_SEG:F045h..F064h) - offsets of various video services //TODO

'/////////////////////////// VIDEO PARAMS TABLE ///////////////////////////////

'(ROM_SEG:F0A4h..F840h) - params to program CGA CRT controller
mem.Dword(0x1D*4)=shl(ROM_SEG,16) or 0xF0A4       'interrupt table vector 1Dh
mem.Pos=ROM_BASE+0xF0A4 'beginning of params table
mem.Bytes=Array(56,40,45,10,31,6,25,28,2,7,6,7,0,0,0,0)    //TEXT 40x25
mem.Bytes=Array(113,80,90,10,31,6,25,25,2,7,6,7,0,0,0,0)   //TEXT 80x25
mem.Bytes=Array(56,40,45,10,127,6,100,112,2,1,6,7,0,0,0,0) //GRAPH
mem.Bytes=Array(97,80,82,15,25,6,25,25,2,13,11,12,0,0,0,0) //Mono

'/////////////////// CGA 8x8 GRAPH FONT TABLE (00h..7Fh chars)/////////////////

'(ROM_SEG:FA6Eh..FE6Dh) - load from file (128 symbols, 8 byte/symbol)
mem.Bytes(ROM_BASE+0xFA6E)=ArrayFile("Fonts\ru8x8.fnt",0,128*8)

'Note: Font for 80h..FFh chars stored in location, pointed by INT 1FH vector 

'//////////////////////// INTERRUPT HANDLERS TABLE ////////////////////////////

'(ROM_SEG:FEF3h..FF52h) - offsets of various interrupt handlers //TODO

'////////////////////////// BIOS CONTROL SUM //////////////////////////////////

'BIOS control sum byte at (ROM_SEG:FFFFh)
dim sum as byte : sum=0 
for i=ROM_BASE to ROM_BASE+0xFFFF : sum=sum+mem.Byte(i) : next
mem.Byte(ROM_BASE+0xFFFF)=0-sum 'control sum must be zero

? "Done!" : end
