From: David Given Date: Sun, 3 Jun 2018 10:13:26 +0000 (+0200) Subject: Add a clean copy of the 8086tiny emulator. X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=c299392fbe17657f27bebfbddf66cd8d181914b3;p=ack.git Add a clean copy of the 8086tiny emulator. --- diff --git a/plat/pc86/emu/8086tiny.c b/plat/pc86/emu/8086tiny.c new file mode 100644 index 000000000..0d3e88089 --- /dev/null +++ b/plat/pc86/emu/8086tiny.c @@ -0,0 +1,760 @@ +// 8086tiny: a tiny, highly functional, highly portable PC emulator/VM +// Copyright 2013-14, Adrian Cable (adrian.cable@gmail.com) - http://www.megalith.co.uk/8086tiny +// +// Revision 1.25 +// +// This work is licensed under the MIT License. See included LICENSE.TXT. + +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#ifndef NO_GRAPHICS +#include "SDL.h" +#endif + +// Emulator system constants +#define IO_PORT_COUNT 0x10000 +#define RAM_SIZE 0x10FFF0 +#define REGS_BASE 0xF0000 +#define VIDEO_RAM_SIZE 0x10000 + +// Graphics/timer/keyboard update delays (explained later) +#ifndef GRAPHICS_UPDATE_DELAY +#define GRAPHICS_UPDATE_DELAY 360000 +#endif +#define KEYBOARD_TIMER_UPDATE_DELAY 20000 + +// 16-bit register decodes +#define REG_AX 0 +#define REG_CX 1 +#define REG_DX 2 +#define REG_BX 3 +#define REG_SP 4 +#define REG_BP 5 +#define REG_SI 6 +#define REG_DI 7 + +#define REG_ES 8 +#define REG_CS 9 +#define REG_SS 10 +#define REG_DS 11 + +#define REG_ZERO 12 +#define REG_SCRATCH 13 + +// 8-bit register decodes +#define REG_AL 0 +#define REG_AH 1 +#define REG_CL 2 +#define REG_CH 3 +#define REG_DL 4 +#define REG_DH 5 +#define REG_BL 6 +#define REG_BH 7 + +// FLAGS register decodes +#define FLAG_CF 40 +#define FLAG_PF 41 +#define FLAG_AF 42 +#define FLAG_ZF 43 +#define FLAG_SF 44 +#define FLAG_TF 45 +#define FLAG_IF 46 +#define FLAG_DF 47 +#define FLAG_OF 48 + +// Lookup tables in the BIOS binary +#define TABLE_XLAT_OPCODE 8 +#define TABLE_XLAT_SUBFUNCTION 9 +#define TABLE_STD_FLAGS 10 +#define TABLE_PARITY_FLAG 11 +#define TABLE_BASE_INST_SIZE 12 +#define TABLE_I_W_SIZE 13 +#define TABLE_I_MOD_SIZE 14 +#define TABLE_COND_JUMP_DECODE_A 15 +#define TABLE_COND_JUMP_DECODE_B 16 +#define TABLE_COND_JUMP_DECODE_C 17 +#define TABLE_COND_JUMP_DECODE_D 18 +#define TABLE_FLAGS_BITFIELDS 19 + +// Bitfields for TABLE_STD_FLAGS values +#define FLAGS_UPDATE_SZP 1 +#define FLAGS_UPDATE_AO_ARITH 2 +#define FLAGS_UPDATE_OC_LOGIC 4 + +// Helper macros + +// Decode mod, r_m and reg fields in instruction +#define DECODE_RM_REG scratch2_uint = 4 * !i_mod, \ + op_to_addr = rm_addr = i_mod < 3 ? SEGREG(seg_override_en ? seg_override : bios_table_lookup[scratch2_uint + 3][i_rm], bios_table_lookup[scratch2_uint][i_rm], regs16[bios_table_lookup[scratch2_uint + 1][i_rm]] + bios_table_lookup[scratch2_uint + 2][i_rm] * i_data1+) : GET_REG_ADDR(i_rm), \ + op_from_addr = GET_REG_ADDR(i_reg), \ + i_d && (scratch_uint = op_from_addr, op_from_addr = rm_addr, op_to_addr = scratch_uint) + +// Return memory-mapped register location (offset into mem array) for register #reg_id +#define GET_REG_ADDR(reg_id) (REGS_BASE + (i_w ? 2 * reg_id : 2 * reg_id + reg_id / 4 & 7)) + +// Returns number of top bit in operand (i.e. 8 for 8-bit operands, 16 for 16-bit operands) +#define TOP_BIT 8*(i_w + 1) + +// Opcode execution unit helpers +#define OPCODE ;break; case +#define OPCODE_CHAIN ; case + +// [I]MUL/[I]DIV/DAA/DAS/ADC/SBB helpers +#define MUL_MACRO(op_data_type,out_regs) (set_opcode(0x10), \ + out_regs[i_w + 1] = (op_result = CAST(op_data_type)mem[rm_addr] * (op_data_type)*out_regs) >> 16, \ + regs16[REG_AX] = op_result, \ + set_OF(set_CF(op_result - (op_data_type)op_result))) +#define DIV_MACRO(out_data_type,in_data_type,out_regs) (scratch_int = CAST(out_data_type)mem[rm_addr]) && !(scratch2_uint = (in_data_type)(scratch_uint = (out_regs[i_w+1] << 16) + regs16[REG_AX]) / scratch_int, scratch2_uint - (out_data_type)scratch2_uint) ? out_regs[i_w+1] = scratch_uint - scratch_int * (*out_regs = scratch2_uint) : pc_interrupt(0) +#define DAA_DAS(op1,op2,mask,min) set_AF((((scratch2_uint = regs8[REG_AL]) & 0x0F) > 9) || regs8[FLAG_AF]) && (op_result = regs8[REG_AL] op1 6, set_CF(regs8[FLAG_CF] || (regs8[REG_AL] op2 scratch2_uint))), \ + set_CF((((mask & 1 ? scratch2_uint : regs8[REG_AL]) & mask) > min) || regs8[FLAG_CF]) && (op_result = regs8[REG_AL] op1 0x60) +#define ADC_SBB_MACRO(a) OP(a##= regs8[FLAG_CF] +), \ + set_CF(regs8[FLAG_CF] && (op_result == op_dest) || (a op_result < a(int)op_dest)), \ + set_AF_OF_arith() + +// Execute arithmetic/logic operations in emulator memory/registers +#define R_M_OP(dest,op,src) (i_w ? op_dest = CAST(unsigned short)dest, op_result = CAST(unsigned short)dest op (op_source = CAST(unsigned short)src) \ + : (op_dest = dest, op_result = dest op (op_source = CAST(unsigned char)src))) +#define MEM_OP(dest,op,src) R_M_OP(mem[dest],op,mem[src]) +#define OP(op) MEM_OP(op_to_addr,op,op_from_addr) + +// Increment or decrement a register #reg_id (usually SI or DI), depending on direction flag and operand size (given by i_w) +#define INDEX_INC(reg_id) (regs16[reg_id] -= (2 * regs8[FLAG_DF] - 1)*(i_w + 1)) + +// Helpers for stack operations +#define R_M_PUSH(a) (i_w = 1, R_M_OP(mem[SEGREG(REG_SS, REG_SP, --)], =, a)) +#define R_M_POP(a) (i_w = 1, regs16[REG_SP] += 2, R_M_OP(a, =, mem[SEGREG(REG_SS, REG_SP, -2+)])) + +// Convert segment:offset to linear address in emulator memory space +#define SEGREG(reg_seg,reg_ofs,op) 16 * regs16[reg_seg] + (unsigned short)(op regs16[reg_ofs]) + +// Returns sign bit of an 8-bit or 16-bit operand +#define SIGN_OF(a) (1 & (i_w ? CAST(short)a : a) >> (TOP_BIT - 1)) + +// Reinterpretation cast +#define CAST(a) *(a*)& + +// Keyboard driver for console. This may need changing for UNIX/non-UNIX platforms +#ifdef _WIN32 +#define KEYBOARD_DRIVER kbhit() && (mem[0x4A6] = getch(), pc_interrupt(7)) +#else +#define KEYBOARD_DRIVER read(0, mem + 0x4A6, 1) && (int8_asap = (mem[0x4A6] == 0x1B), pc_interrupt(7)) +#endif + +// Keyboard driver for SDL +#ifdef NO_GRAPHICS +#define SDL_KEYBOARD_DRIVER KEYBOARD_DRIVER +#else +#define SDL_KEYBOARD_DRIVER sdl_screen ? SDL_PollEvent(&sdl_event) && (sdl_event.type == SDL_KEYDOWN || sdl_event.type == SDL_KEYUP) && (scratch_uint = sdl_event.key.keysym.unicode, scratch2_uint = sdl_event.key.keysym.mod, CAST(short)mem[0x4A6] = 0x400 + 0x800*!!(scratch2_uint & KMOD_ALT) + 0x1000*!!(scratch2_uint & KMOD_SHIFT) + 0x2000*!!(scratch2_uint & KMOD_CTRL) + 0x4000*(sdl_event.type == SDL_KEYUP) + ((!scratch_uint || scratch_uint > 0x7F) ? sdl_event.key.keysym.sym : scratch_uint), pc_interrupt(7)) : (KEYBOARD_DRIVER) +#endif + +// Global variable definitions +unsigned char mem[RAM_SIZE], io_ports[IO_PORT_COUNT], *opcode_stream, *regs8, i_rm, i_w, i_reg, i_mod, i_mod_size, i_d, i_reg4bit, raw_opcode_id, xlat_opcode_id, extra, rep_mode, seg_override_en, rep_override_en, trap_flag, int8_asap, scratch_uchar, io_hi_lo, *vid_mem_base, spkr_en, bios_table_lookup[20][256]; +unsigned short *regs16, reg_ip, seg_override, file_index, wave_counter; +unsigned int op_source, op_dest, rm_addr, op_to_addr, op_from_addr, i_data0, i_data1, i_data2, scratch_uint, scratch2_uint, inst_counter, set_flags_type, GRAPHICS_X, GRAPHICS_Y, pixel_colors[16], vmem_ctr; +int op_result, disk[3], scratch_int; +time_t clock_buf; +struct timeb ms_clock; + +#ifndef NO_GRAPHICS +SDL_AudioSpec sdl_audio = {44100, AUDIO_U8, 1, 0, 128}; +SDL_Surface *sdl_screen; +SDL_Event sdl_event; +unsigned short vid_addr_lookup[VIDEO_RAM_SIZE], cga_colors[4] = {0 /* Black */, 0x1F1F /* Cyan */, 0xE3E3 /* Magenta */, 0xFFFF /* White */}; +#endif + +// Helper functions + +// Set carry flag +char set_CF(int new_CF) +{ + return regs8[FLAG_CF] = !!new_CF; +} + +// Set auxiliary flag +char set_AF(int new_AF) +{ + return regs8[FLAG_AF] = !!new_AF; +} + +// Set overflow flag +char set_OF(int new_OF) +{ + return regs8[FLAG_OF] = !!new_OF; +} + +// Set auxiliary and overflow flag after arithmetic operations +char set_AF_OF_arith() +{ + set_AF((op_source ^= op_dest ^ op_result) & 0x10); + if (op_result == op_dest) + return set_OF(0); + else + return set_OF(1 & (regs8[FLAG_CF] ^ op_source >> (TOP_BIT - 1))); +} + +// Assemble and return emulated CPU FLAGS register in scratch_uint +void make_flags() +{ + scratch_uint = 0xF002; // 8086 has reserved and unused flags set to 1 + for (int i = 9; i--;) + scratch_uint += regs8[FLAG_CF + i] << bios_table_lookup[TABLE_FLAGS_BITFIELDS][i]; +} + +// Set emulated CPU FLAGS register from regs8[FLAG_xx] values +void set_flags(int new_flags) +{ + for (int i = 9; i--;) + regs8[FLAG_CF + i] = !!(1 << bios_table_lookup[TABLE_FLAGS_BITFIELDS][i] & new_flags); +} + +// Convert raw opcode to translated opcode index. This condenses a large number of different encodings of similar +// instructions into a much smaller number of distinct functions, which we then execute +void set_opcode(unsigned char opcode) +{ + xlat_opcode_id = bios_table_lookup[TABLE_XLAT_OPCODE][raw_opcode_id = opcode]; + extra = bios_table_lookup[TABLE_XLAT_SUBFUNCTION][opcode]; + i_mod_size = bios_table_lookup[TABLE_I_MOD_SIZE][opcode]; + set_flags_type = bios_table_lookup[TABLE_STD_FLAGS][opcode]; +} + +// Execute INT #interrupt_num on the emulated machine +char pc_interrupt(unsigned char interrupt_num) +{ + set_opcode(0xCD); // Decode like INT + + make_flags(); + R_M_PUSH(scratch_uint); + R_M_PUSH(regs16[REG_CS]); + R_M_PUSH(reg_ip); + MEM_OP(REGS_BASE + 2 * REG_CS, =, 4 * interrupt_num + 2); + R_M_OP(reg_ip, =, mem[4 * interrupt_num]); + + return regs8[FLAG_TF] = regs8[FLAG_IF] = 0; +} + +// AAA and AAS instructions - which_operation is +1 for AAA, and -1 for AAS +int AAA_AAS(char which_operation) +{ + return (regs16[REG_AX] += 262 * which_operation*set_AF(set_CF(((regs8[REG_AL] & 0x0F) > 9) || regs8[FLAG_AF])), regs8[REG_AL] &= 0x0F); +} + +#ifndef NO_GRAPHICS +void audio_callback(void *data, unsigned char *stream, int len) +{ + for (int i = 0; i < len; i++) + stream[i] = (spkr_en == 3) && CAST(unsigned short)mem[0x4AA] ? -((54 * wave_counter++ / CAST(unsigned short)mem[0x4AA]) & 1) : sdl_audio.silence; + + spkr_en = io_ports[0x61] & 3; +} +#endif + +// Emulator entry point +int main(int argc, char **argv) +{ +#ifndef NO_GRAPHICS + // Initialise SDL + SDL_Init(SDL_INIT_AUDIO); + sdl_audio.callback = audio_callback; +#ifdef _WIN32 + sdl_audio.samples = 512; +#endif + SDL_OpenAudio(&sdl_audio, 0); +#endif + + // regs16 and reg8 point to F000:0, the start of memory-mapped registers. CS is initialised to F000 + regs16 = (unsigned short *)(regs8 = mem + REGS_BASE); + regs16[REG_CS] = 0xF000; + + // Trap flag off + regs8[FLAG_TF] = 0; + + // Set DL equal to the boot device: 0 for the FD, or 0x80 for the HD. Normally, boot from the FD. + // But, if the HD image file is prefixed with @, then boot from the HD + regs8[REG_DL] = ((argc > 3) && (*argv[3] == '@')) ? argv[3]++, 0x80 : 0; + + // Open BIOS (file id disk[2]), floppy disk image (disk[1]), and hard disk image (disk[0]) if specified + for (file_index = 3; file_index;) + disk[--file_index] = *++argv ? open(*argv, 32898) : 0; + + // Set CX:AX equal to the hard disk image size, if present + CAST(unsigned)regs16[REG_AX] = *disk ? lseek(*disk, 0, 2) >> 9 : 0; + + // Load BIOS image into F000:0100, and set IP to 0100 + read(disk[2], regs8 + (reg_ip = 0x100), 0xFF00); + + // Load instruction decoding helper table + for (int i = 0; i < 20; i++) + for (int j = 0; j < 256; j++) + bios_table_lookup[i][j] = regs8[regs16[0x81 + i] + j]; + + // Instruction execution loop. Terminates if CS:IP = 0:0 + for (; opcode_stream = mem + 16 * regs16[REG_CS] + reg_ip, opcode_stream != mem;) + { + // Set up variables to prepare for decoding an opcode + set_opcode(*opcode_stream); + + // Extract i_w and i_d fields from instruction + i_w = (i_reg4bit = raw_opcode_id & 7) & 1; + i_d = i_reg4bit / 2 & 1; + + // Extract instruction data fields + i_data0 = CAST(short)opcode_stream[1]; + i_data1 = CAST(short)opcode_stream[2]; + i_data2 = CAST(short)opcode_stream[3]; + + // seg_override_en and rep_override_en contain number of instructions to hold segment override and REP prefix respectively + if (seg_override_en) + seg_override_en--; + if (rep_override_en) + rep_override_en--; + + // i_mod_size > 0 indicates that opcode uses i_mod/i_rm/i_reg, so decode them + if (i_mod_size) + { + i_mod = (i_data0 & 0xFF) >> 6; + i_rm = i_data0 & 7; + i_reg = i_data0 / 8 & 7; + + if ((!i_mod && i_rm == 6) || (i_mod == 2)) + i_data2 = CAST(short)opcode_stream[4]; + else if (i_mod != 1) + i_data2 = i_data1; + else // If i_mod is 1, operand is (usually) 8 bits rather than 16 bits + i_data1 = (char)i_data1; + + DECODE_RM_REG; + } + + // Instruction execution unit + switch (xlat_opcode_id) + { + OPCODE_CHAIN 0: // Conditional jump (JAE, JNAE, etc.) + // i_w is the invert flag, e.g. i_w == 1 means JNAE, whereas i_w == 0 means JAE + scratch_uchar = raw_opcode_id / 2 & 7; + reg_ip += (char)i_data0 * (i_w ^ (regs8[bios_table_lookup[TABLE_COND_JUMP_DECODE_A][scratch_uchar]] || regs8[bios_table_lookup[TABLE_COND_JUMP_DECODE_B][scratch_uchar]] || regs8[bios_table_lookup[TABLE_COND_JUMP_DECODE_C][scratch_uchar]] ^ regs8[bios_table_lookup[TABLE_COND_JUMP_DECODE_D][scratch_uchar]])) + OPCODE 1: // MOV reg, imm + i_w = !!(raw_opcode_id & 8); + R_M_OP(mem[GET_REG_ADDR(i_reg4bit)], =, i_data0) + OPCODE 3: // PUSH regs16 + R_M_PUSH(regs16[i_reg4bit]) + OPCODE 4: // POP regs16 + R_M_POP(regs16[i_reg4bit]) + OPCODE 2: // INC|DEC regs16 + i_w = 1; + i_d = 0; + i_reg = i_reg4bit; + DECODE_RM_REG; + i_reg = extra + OPCODE_CHAIN 5: // INC|DEC|JMP|CALL|PUSH + if (i_reg < 2) // INC|DEC + MEM_OP(op_from_addr, += 1 - 2 * i_reg +, REGS_BASE + 2 * REG_ZERO), + op_source = 1, + set_AF_OF_arith(), + set_OF(op_dest + 1 - i_reg == 1 << (TOP_BIT - 1)), + (xlat_opcode_id == 5) && (set_opcode(0x10), 0); // Decode like ADC + else if (i_reg != 6) // JMP|CALL + i_reg - 3 || R_M_PUSH(regs16[REG_CS]), // CALL (far) + i_reg & 2 && R_M_PUSH(reg_ip + 2 + i_mod*(i_mod != 3) + 2*(!i_mod && i_rm == 6)), // CALL (near or far) + i_reg & 1 && (regs16[REG_CS] = CAST(short)mem[op_from_addr + 2]), // JMP|CALL (far) + R_M_OP(reg_ip, =, mem[op_from_addr]), + set_opcode(0x9A); // Decode like CALL + else // PUSH + R_M_PUSH(mem[rm_addr]) + OPCODE 6: // TEST r/m, imm16 / NOT|NEG|MUL|IMUL|DIV|IDIV reg + op_to_addr = op_from_addr; + + switch (i_reg) + { + OPCODE_CHAIN 0: // TEST + set_opcode(0x20); // Decode like AND + reg_ip += i_w + 1; + R_M_OP(mem[op_to_addr], &, i_data2) + OPCODE 2: // NOT + OP(=~) + OPCODE 3: // NEG + OP(=-); + op_dest = 0; + set_opcode(0x28); // Decode like SUB + set_CF(op_result > op_dest) + OPCODE 4: // MUL + i_w ? MUL_MACRO(unsigned short, regs16) : MUL_MACRO(unsigned char, regs8) + OPCODE 5: // IMUL + i_w ? MUL_MACRO(short, regs16) : MUL_MACRO(char, regs8) + OPCODE 6: // DIV + i_w ? DIV_MACRO(unsigned short, unsigned, regs16) : DIV_MACRO(unsigned char, unsigned short, regs8) + OPCODE 7: // IDIV + i_w ? DIV_MACRO(short, int, regs16) : DIV_MACRO(char, short, regs8); + } + OPCODE 7: // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP AL/AX, immed + rm_addr = REGS_BASE; + i_data2 = i_data0; + i_mod = 3; + i_reg = extra; + reg_ip--; + OPCODE_CHAIN 8: // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP reg, immed + op_to_addr = rm_addr; + regs16[REG_SCRATCH] = (i_d |= !i_w) ? (char)i_data2 : i_data2; + op_from_addr = REGS_BASE + 2 * REG_SCRATCH; + reg_ip += !i_d + 1; + set_opcode(0x08 * (extra = i_reg)); + OPCODE_CHAIN 9: // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP|MOV reg, r/m + switch (extra) + { + OPCODE_CHAIN 0: // ADD + OP(+=), + set_CF(op_result < op_dest) + OPCODE 1: // OR + OP(|=) + OPCODE 2: // ADC + ADC_SBB_MACRO(+) + OPCODE 3: // SBB + ADC_SBB_MACRO(-) + OPCODE 4: // AND + OP(&=) + OPCODE 5: // SUB + OP(-=), + set_CF(op_result > op_dest) + OPCODE 6: // XOR + OP(^=) + OPCODE 7: // CMP + OP(-), + set_CF(op_result > op_dest) + OPCODE 8: // MOV + OP(=); + } + OPCODE 10: // MOV sreg, r/m | POP r/m | LEA reg, r/m + if (!i_w) // MOV + i_w = 1, + i_reg += 8, + DECODE_RM_REG, + OP(=); + else if (!i_d) // LEA + seg_override_en = 1, + seg_override = REG_ZERO, + DECODE_RM_REG, + R_M_OP(mem[op_from_addr], =, rm_addr); + else // POP + R_M_POP(mem[rm_addr]) + OPCODE 11: // MOV AL/AX, [loc] + i_mod = i_reg = 0; + i_rm = 6; + i_data1 = i_data0; + DECODE_RM_REG; + MEM_OP(op_from_addr, =, op_to_addr) + OPCODE 12: // ROL|ROR|RCL|RCR|SHL|SHR|???|SAR reg/mem, 1/CL/imm (80186) + scratch2_uint = SIGN_OF(mem[rm_addr]), + scratch_uint = extra ? // xxx reg/mem, imm + ++reg_ip, + (char)i_data1 + : // xxx reg/mem, CL + i_d + ? 31 & regs8[REG_CL] + : // xxx reg/mem, 1 + 1; + if (scratch_uint) + { + if (i_reg < 4) // Rotate operations + scratch_uint %= i_reg / 2 + TOP_BIT, + R_M_OP(scratch2_uint, =, mem[rm_addr]); + if (i_reg & 1) // Rotate/shift right operations + R_M_OP(mem[rm_addr], >>=, scratch_uint); + else // Rotate/shift left operations + R_M_OP(mem[rm_addr], <<=, scratch_uint); + if (i_reg > 3) // Shift operations + set_opcode(0x10); // Decode like ADC + if (i_reg > 4) // SHR or SAR + set_CF(op_dest >> (scratch_uint - 1) & 1); + } + + switch (i_reg) + { + OPCODE_CHAIN 0: // ROL + R_M_OP(mem[rm_addr], += , scratch2_uint >> (TOP_BIT - scratch_uint)); + set_OF(SIGN_OF(op_result) ^ set_CF(op_result & 1)) + OPCODE 1: // ROR + scratch2_uint &= (1 << scratch_uint) - 1, + R_M_OP(mem[rm_addr], += , scratch2_uint << (TOP_BIT - scratch_uint)); + set_OF(SIGN_OF(op_result * 2) ^ set_CF(SIGN_OF(op_result))) + OPCODE 2: // RCL + R_M_OP(mem[rm_addr], += (regs8[FLAG_CF] << (scratch_uint - 1)) + , scratch2_uint >> (1 + TOP_BIT - scratch_uint)); + set_OF(SIGN_OF(op_result) ^ set_CF(scratch2_uint & 1 << (TOP_BIT - scratch_uint))) + OPCODE 3: // RCR + R_M_OP(mem[rm_addr], += (regs8[FLAG_CF] << (TOP_BIT - scratch_uint)) + , scratch2_uint << (1 + TOP_BIT - scratch_uint)); + set_CF(scratch2_uint & 1 << (scratch_uint - 1)); + set_OF(SIGN_OF(op_result) ^ SIGN_OF(op_result * 2)) + OPCODE 4: // SHL + set_OF(SIGN_OF(op_result) ^ set_CF(SIGN_OF(op_dest << (scratch_uint - 1)))) + OPCODE 5: // SHR + set_OF(SIGN_OF(op_dest)) + OPCODE 7: // SAR + scratch_uint < TOP_BIT || set_CF(scratch2_uint); + set_OF(0); + R_M_OP(mem[rm_addr], +=, scratch2_uint *= ~(((1 << TOP_BIT) - 1) >> scratch_uint)); + } + OPCODE 13: // LOOPxx|JCZX + scratch_uint = !!--regs16[REG_CX]; + + switch(i_reg4bit) + { + OPCODE_CHAIN 0: // LOOPNZ + scratch_uint &= !regs8[FLAG_ZF] + OPCODE 1: // LOOPZ + scratch_uint &= regs8[FLAG_ZF] + OPCODE 3: // JCXXZ + scratch_uint = !++regs16[REG_CX]; + } + reg_ip += scratch_uint*(char)i_data0 + OPCODE 14: // JMP | CALL short/near + reg_ip += 3 - i_d; + if (!i_w) + { + if (i_d) // JMP far + reg_ip = 0, + regs16[REG_CS] = i_data2; + else // CALL + R_M_PUSH(reg_ip); + } + reg_ip += i_d && i_w ? (char)i_data0 : i_data0 + OPCODE 15: // TEST reg, r/m + MEM_OP(op_from_addr, &, op_to_addr) + OPCODE 16: // XCHG AX, regs16 + i_w = 1; + op_to_addr = REGS_BASE; + op_from_addr = GET_REG_ADDR(i_reg4bit); + OPCODE_CHAIN 24: // NOP|XCHG reg, r/m + if (op_to_addr != op_from_addr) + OP(^=), + MEM_OP(op_from_addr, ^=, op_to_addr), + OP(^=) + OPCODE 17: // MOVSx (extra=0)|STOSx (extra=1)|LODSx (extra=2) + scratch2_uint = seg_override_en ? seg_override : REG_DS; + + for (scratch_uint = rep_override_en ? regs16[REG_CX] : 1; scratch_uint; scratch_uint--) + { + MEM_OP(extra < 2 ? SEGREG(REG_ES, REG_DI,) : REGS_BASE, =, extra & 1 ? REGS_BASE : SEGREG(scratch2_uint, REG_SI,)), + extra & 1 || INDEX_INC(REG_SI), + extra & 2 || INDEX_INC(REG_DI); + } + + if (rep_override_en) + regs16[REG_CX] = 0 + OPCODE 18: // CMPSx (extra=0)|SCASx (extra=1) + scratch2_uint = seg_override_en ? seg_override : REG_DS; + + if ((scratch_uint = rep_override_en ? regs16[REG_CX] : 1)) + { + for (; scratch_uint; rep_override_en || scratch_uint--) + { + MEM_OP(extra ? REGS_BASE : SEGREG(scratch2_uint, REG_SI,), -, SEGREG(REG_ES, REG_DI,)), + extra || INDEX_INC(REG_SI), + INDEX_INC(REG_DI), rep_override_en && !(--regs16[REG_CX] && (!op_result == rep_mode)) && (scratch_uint = 0); + } + + set_flags_type = FLAGS_UPDATE_SZP | FLAGS_UPDATE_AO_ARITH; // Funge to set SZP/AO flags + set_CF(op_result > op_dest); + } + OPCODE 19: // RET|RETF|IRET + i_d = i_w; + R_M_POP(reg_ip); + if (extra) // IRET|RETF|RETF imm16 + R_M_POP(regs16[REG_CS]); + if (extra & 2) // IRET + set_flags(R_M_POP(scratch_uint)); + else if (!i_d) // RET|RETF imm16 + regs16[REG_SP] += i_data0 + OPCODE 20: // MOV r/m, immed + R_M_OP(mem[op_from_addr], =, i_data2) + OPCODE 21: // IN AL/AX, DX/imm8 + io_ports[0x20] = 0; // PIC EOI + io_ports[0x42] = --io_ports[0x40]; // PIT channel 0/2 read placeholder + io_ports[0x3DA] ^= 9; // CGA refresh + scratch_uint = extra ? regs16[REG_DX] : (unsigned char)i_data0; + scratch_uint == 0x60 && (io_ports[0x64] = 0); // Scancode read flag + scratch_uint == 0x3D5 && (io_ports[0x3D4] >> 1 == 7) && (io_ports[0x3D5] = ((mem[0x49E]*80 + mem[0x49D] + CAST(short)mem[0x4AD]) & (io_ports[0x3D4] & 1 ? 0xFF : 0xFF00)) >> (io_ports[0x3D4] & 1 ? 0 : 8)); // CRT cursor position + R_M_OP(regs8[REG_AL], =, io_ports[scratch_uint]); + OPCODE 22: // OUT DX/imm8, AL/AX + scratch_uint = extra ? regs16[REG_DX] : (unsigned char)i_data0; + R_M_OP(io_ports[scratch_uint], =, regs8[REG_AL]); + scratch_uint == 0x61 && (io_hi_lo = 0, spkr_en |= regs8[REG_AL] & 3); // Speaker control + (scratch_uint == 0x40 || scratch_uint == 0x42) && (io_ports[0x43] & 6) && (mem[0x469 + scratch_uint - (io_hi_lo ^= 1)] = regs8[REG_AL]); // PIT rate programming +#ifndef NO_GRAPHICS + scratch_uint == 0x43 && (io_hi_lo = 0, regs8[REG_AL] >> 6 == 2) && (SDL_PauseAudio((regs8[REG_AL] & 0xF7) != 0xB6), 0); // Speaker enable +#endif + scratch_uint == 0x3D5 && (io_ports[0x3D4] >> 1 == 6) && (mem[0x4AD + !(io_ports[0x3D4] & 1)] = regs8[REG_AL]); // CRT video RAM start offset + scratch_uint == 0x3D5 && (io_ports[0x3D4] >> 1 == 7) && (scratch2_uint = ((mem[0x49E]*80 + mem[0x49D] + CAST(short)mem[0x4AD]) & (io_ports[0x3D4] & 1 ? 0xFF00 : 0xFF)) + (regs8[REG_AL] << (io_ports[0x3D4] & 1 ? 0 : 8)) - CAST(short)mem[0x4AD], mem[0x49D] = scratch2_uint % 80, mem[0x49E] = scratch2_uint / 80); // CRT cursor position + scratch_uint == 0x3B5 && io_ports[0x3B4] == 1 && (GRAPHICS_X = regs8[REG_AL] * 16); // Hercules resolution reprogramming. Defaults are set in the BIOS + scratch_uint == 0x3B5 && io_ports[0x3B4] == 6 && (GRAPHICS_Y = regs8[REG_AL] * 4); + OPCODE 23: // REPxx + rep_override_en = 2; + rep_mode = i_w; + seg_override_en && seg_override_en++ + OPCODE 25: // PUSH reg + R_M_PUSH(regs16[extra]) + OPCODE 26: // POP reg + R_M_POP(regs16[extra]) + OPCODE 27: // xS: segment overrides + seg_override_en = 2; + seg_override = extra; + rep_override_en && rep_override_en++ + OPCODE 28: // DAA/DAS + i_w = 0; + extra ? DAA_DAS(-=, >=, 0xFF, 0x99) : DAA_DAS(+=, <, 0xF0, 0x90) // extra = 0 for DAA, 1 for DAS + OPCODE 29: // AAA/AAS + op_result = AAA_AAS(extra - 1) + OPCODE 30: // CBW + regs8[REG_AH] = -SIGN_OF(regs8[REG_AL]) + OPCODE 31: // CWD + regs16[REG_DX] = -SIGN_OF(regs16[REG_AX]) + OPCODE 32: // CALL FAR imm16:imm16 + R_M_PUSH(regs16[REG_CS]); + R_M_PUSH(reg_ip + 5); + regs16[REG_CS] = i_data2; + reg_ip = i_data0 + OPCODE 33: // PUSHF + make_flags(); + R_M_PUSH(scratch_uint) + OPCODE 34: // POPF + set_flags(R_M_POP(scratch_uint)) + OPCODE 35: // SAHF + make_flags(); + set_flags((scratch_uint & 0xFF00) + regs8[REG_AH]) + OPCODE 36: // LAHF + make_flags(), + regs8[REG_AH] = scratch_uint + OPCODE 37: // LES|LDS reg, r/m + i_w = i_d = 1; + DECODE_RM_REG; + OP(=); + MEM_OP(REGS_BASE + extra, =, rm_addr + 2) + OPCODE 38: // INT 3 + ++reg_ip; + pc_interrupt(3) + OPCODE 39: // INT imm8 + reg_ip += 2; + pc_interrupt(i_data0) + OPCODE 40: // INTO + ++reg_ip; + regs8[FLAG_OF] && pc_interrupt(4) + OPCODE 41: // AAM + if (i_data0 &= 0xFF) + regs8[REG_AH] = regs8[REG_AL] / i_data0, + op_result = regs8[REG_AL] %= i_data0; + else // Divide by zero + pc_interrupt(0) + OPCODE 42: // AAD + i_w = 0; + regs16[REG_AX] = op_result = 0xFF & regs8[REG_AL] + i_data0 * regs8[REG_AH] + OPCODE 43: // SALC + regs8[REG_AL] = -regs8[FLAG_CF] + OPCODE 44: // XLAT + regs8[REG_AL] = mem[SEGREG(seg_override_en ? seg_override : REG_DS, REG_BX, regs8[REG_AL] +)] + OPCODE 45: // CMC + regs8[FLAG_CF] ^= 1 + OPCODE 46: // CLC|STC|CLI|STI|CLD|STD + regs8[extra / 2] = extra & 1 + OPCODE 47: // TEST AL/AX, immed + R_M_OP(regs8[REG_AL], &, i_data0) + OPCODE 48: // Emulator-specific 0F xx opcodes + switch ((char)i_data0) + { + OPCODE_CHAIN 0: // PUTCHAR_AL + write(1, regs8, 1) + OPCODE 1: // GET_RTC + time(&clock_buf); + ftime(&ms_clock); + memcpy(mem + SEGREG(REG_ES, REG_BX,), localtime(&clock_buf), sizeof(struct tm)); + CAST(short)mem[SEGREG(REG_ES, REG_BX, 36+)] = ms_clock.millitm; + OPCODE 2: // DISK_READ + OPCODE_CHAIN 3: // DISK_WRITE + regs8[REG_AL] = ~lseek(disk[regs8[REG_DL]], CAST(unsigned)regs16[REG_BP] << 9, 0) + ? ((char)i_data0 == 3 ? (int(*)())write : (int(*)())read)(disk[regs8[REG_DL]], mem + SEGREG(REG_ES, REG_BX,), regs16[REG_AX]) + : 0; + } + } + + // Increment instruction pointer by computed instruction length. Tables in the BIOS binary + // help us here. + reg_ip += (i_mod*(i_mod != 3) + 2*(!i_mod && i_rm == 6))*i_mod_size + bios_table_lookup[TABLE_BASE_INST_SIZE][raw_opcode_id] + bios_table_lookup[TABLE_I_W_SIZE][raw_opcode_id]*(i_w + 1); + + // If instruction needs to update SF, ZF and PF, set them as appropriate + if (set_flags_type & FLAGS_UPDATE_SZP) + { + regs8[FLAG_SF] = SIGN_OF(op_result); + regs8[FLAG_ZF] = !op_result; + regs8[FLAG_PF] = bios_table_lookup[TABLE_PARITY_FLAG][(unsigned char)op_result]; + + // If instruction is an arithmetic or logic operation, also set AF/OF/CF as appropriate. + if (set_flags_type & FLAGS_UPDATE_AO_ARITH) + set_AF_OF_arith(); + if (set_flags_type & FLAGS_UPDATE_OC_LOGIC) + set_CF(0), set_OF(0); + } + + // Poll timer/keyboard every KEYBOARD_TIMER_UPDATE_DELAY instructions + if (!(++inst_counter % KEYBOARD_TIMER_UPDATE_DELAY)) + int8_asap = 1; + +#ifndef NO_GRAPHICS + // Update the video graphics display every GRAPHICS_UPDATE_DELAY instructions + if (!(inst_counter % GRAPHICS_UPDATE_DELAY)) + { + // Video card in graphics mode? + if (io_ports[0x3B8] & 2) + { + // If we don't already have an SDL window open, set it up and compute color and video memory translation tables + if (!sdl_screen) + { + for (int i = 0; i < 16; i++) + pixel_colors[i] = mem[0x4AC] ? // CGA? + cga_colors[(i & 12) >> 2] + (cga_colors[i & 3] << 16) // CGA -> RGB332 + : 0xFF*(((i & 1) << 24) + ((i & 2) << 15) + ((i & 4) << 6) + ((i & 8) >> 3)); // Hercules -> RGB332 + + for (int i = 0; i < GRAPHICS_X * GRAPHICS_Y / 4; i++) + vid_addr_lookup[i] = i / GRAPHICS_X * (GRAPHICS_X / 8) + (i / 2) % (GRAPHICS_X / 8) + 0x2000*(mem[0x4AC] ? (2 * i / GRAPHICS_X) % 2 : (4 * i / GRAPHICS_X) % 4); + + SDL_Init(SDL_INIT_VIDEO); + sdl_screen = SDL_SetVideoMode(GRAPHICS_X, GRAPHICS_Y, 8, 0); + SDL_EnableUNICODE(1); + SDL_EnableKeyRepeat(500, 30); + } + + // Refresh SDL display from emulated graphics card video RAM + vid_mem_base = mem + 0xB0000 + 0x8000*(mem[0x4AC] ? 1 : io_ports[0x3B8] >> 7); // B800:0 for CGA/Hercules bank 2, B000:0 for Hercules bank 1 + for (int i = 0; i < GRAPHICS_X * GRAPHICS_Y / 4; i++) + ((unsigned *)sdl_screen->pixels)[i] = pixel_colors[15 & (vid_mem_base[vid_addr_lookup[i]] >> 4*!(i & 1))]; + + SDL_Flip(sdl_screen); + } + else if (sdl_screen) // Application has gone back to text mode, so close the SDL window + { + SDL_QuitSubSystem(SDL_INIT_VIDEO); + sdl_screen = 0; + } + SDL_PumpEvents(); + } +#endif + + // Application has set trap flag, so fire INT 1 + if (trap_flag) + pc_interrupt(1); + + trap_flag = regs8[FLAG_TF]; + + // If a timer tick is pending, interrupts are enabled, and no overrides/REP are active, + // then process the tick and check for new keystrokes + if (int8_asap && !seg_override_en && !rep_override_en && regs8[FLAG_IF] && !regs8[FLAG_TF]) + pc_interrupt(0xA), int8_asap = 0, SDL_KEYBOARD_DRIVER; + } + +#ifndef NO_GRAPHICS + SDL_Quit(); +#endif + return 0; +} diff --git a/plat/pc86/emu/Makefile b/plat/pc86/emu/Makefile new file mode 100644 index 000000000..e97681701 --- /dev/null +++ b/plat/pc86/emu/Makefile @@ -0,0 +1,28 @@ +# 8086tiny: a tiny, highly functional, highly portable PC emulator/VM +# Copyright 2013-14, Adrian Cable (adrian.cable@gmail.com) - http://www.megalith.co.uk/8086tiny +# +# This work is licensed under the MIT License. See included LICENSE.TXT. + +# 8086tiny builds with graphics and sound support +# 8086tiny_slowcpu improves graphics performance on slow platforms (e.g. Raspberry Pi) +# no_graphics compiles without SDL graphics/sound + +OPTS_ALL=-O3 -fsigned-char -std=c99 +OPTS_SDL=`sdl-config --cflags --libs` +OPTS_NOGFX=-DNO_GRAPHICS +OPTS_SLOWCPU=-DGRAPHICS_UPDATE_DELAY=25000 + +8086tiny: 8086tiny.c + ${CC} 8086tiny.c ${OPTS_SDL} ${OPTS_ALL} -o 8086tiny + strip 8086tiny + +8086tiny_slowcpu: 8086tiny.c + ${CC} 8086tiny.c ${OPTS_SDL} ${OPTS_ALL} ${OPTS_SLOWCPU} -o 8086tiny + strip 8086tiny + +no_graphics: 8086tiny.c + ${CC} 8086tiny.c ${OPTS_NOGFX} ${OPTS_ALL} -o 8086tiny + strip 8086tiny + +clean: + rm 8086tiny diff --git a/plat/pc86/emu/README.md b/plat/pc86/emu/README.md new file mode 100644 index 000000000..87bd0129b --- /dev/null +++ b/plat/pc86/emu/README.md @@ -0,0 +1,10 @@ +8086tiny +======== + +8086tiny is a completely free (MIT License) open source PC XT-compatible emulator/virtual machine written in C. It is, we believe, the smallest of its kind (the fully-commented source is under 25K). Despite its size, 8086tiny provides a highly accurate 8086 CPU emulation, together with support for PC peripherals including XT-style keyboard, floppy/hard disk, clock, audio, and Hercules/CGA graphics. 8086tiny is powerful enough to run software like AutoCAD, Windows 3.0, and legacy PC games: the 8086tiny distribution includes Alley Cat, the author's favorite PC game of all time. + +8086tiny is highly portable and runs on practically any little endian machine, from simple 32-bit MCUs upwards. 8086tiny has successfully been deployed on 32-bit/64-bit Intel machines (Windows, Mac OS X and Linux), Nexus 4/ARM (Android), iPad 3 and iPhone 5S (iOS), and Raspberry Pi (Linux). + +The philosophy of 8086tiny is to keep the code base as small as possible, and through the open source license encourage individual developers to tune and extend it as per their specific requirements, adding support, for example, for more complex instruction sets (e.g. Pentium) or peripherals (e.g. mouse). Forking this repository is highly encouraged! + +Any questions, comments or suggestions are very welcome in our forum at 8086tiny.freeforums.net. diff --git a/plat/pc86/emu/bios b/plat/pc86/emu/bios new file mode 100644 index 000000000..2d2ffe31f Binary files /dev/null and b/plat/pc86/emu/bios differ diff --git a/plat/pc86/emu/bios_source/bios.asm b/plat/pc86/emu/bios_source/bios.asm new file mode 100644 index 000000000..93eca8521 --- /dev/null +++ b/plat/pc86/emu/bios_source/bios.asm @@ -0,0 +1,3851 @@ +; BIOS source for 8086tiny IBM PC emulator (revision 1.21 and above). Compiles with NASM. +; Copyright 2013-14, Adrian Cable (adrian.cable@gmail.com) - http://www.megalith.co.uk/8086tiny +; +; Revision 1.61 +; +; This work is licensed under the MIT License. See included LICENSE.TXT. + + cpu 8086 + +; Here we define macros for some custom instructions that help the emulator talk with the outside +; world. They are described in detail in the hint.html file, which forms part of the emulator +; distribution. + +%macro extended_putchar_al 0 + db 0x0f, 0x00 +%endmacro + +%macro extended_get_rtc 0 + db 0x0f, 0x01 +%endmacro + +%macro extended_read_disk 0 + db 0x0f, 0x02 +%endmacro + +%macro extended_write_disk 0 + db 0x0f, 0x03 +%endmacro + +org 100h ; BIOS loads at offset 0x0100 + +main: + + jmp bios_entry + +; Here go pointers to the different data tables used for instruction decoding + + dw rm_mode12_reg1 ; Table 0: R/M mode 1/2 "register 1" lookup + dw rm_mode012_reg2 ; Table 1: R/M mode 1/2 "register 2" lookup + dw rm_mode12_disp ; Table 2: R/M mode 1/2 "DISP multiplier" lookup + dw rm_mode12_dfseg ; Table 3: R/M mode 1/2 "default segment" lookup + dw rm_mode0_reg1 ; Table 4: R/M mode 0 "register 1" lookup + dw rm_mode012_reg2 ; Table 5: R/M mode 0 "register 2" lookup + dw rm_mode0_disp ; Table 6: R/M mode 0 "DISP multiplier" lookup + dw rm_mode0_dfseg ; Table 7: R/M mode 0 "default segment" lookup + dw xlat_ids ; Table 8: Translation of raw opcode index ("Raw ID") to function number ("Xlat'd ID") + dw ex_data ; Table 9: Translation of Raw ID to Extra Data + dw std_flags ; Table 10: How each Raw ID sets the flags (bit 1 = sets SZP, bit 2 = sets AF/OF for arithmetic, bit 3 = sets OF/CF for logic) + dw parity ; Table 11: Parity flag loop-up table (256 entries) + dw base_size ; Table 12: Translation of Raw ID to base instruction size (bytes) + dw i_w_adder ; Table 13: Translation of Raw ID to i_w size adder yes/no + dw i_mod_adder ; Table 14: Translation of Raw ID to i_mod size adder yes/no + dw jxx_dec_a ; Table 15: Jxx decode table A + dw jxx_dec_b ; Table 16: Jxx decode table B + dw jxx_dec_c ; Table 17: Jxx decode table C + dw jxx_dec_d ; Table 18: Jxx decode table D + dw flags_mult ; Table 19: FLAGS multipliers + +; These values (BIOS ID string, BIOS date and so forth) go at the very top of memory + +biosstr db '8086tiny BIOS Revision 1.61!', 0, 0 ; Why not? +mem_top db 0xea, 0, 0x01, 0, 0xf0, '03/08/14', 0, 0xfe, 0 + +bios_entry: + + ; Set up initial stack to F000:F000 + + mov sp, 0xf000 + mov ss, sp + + push cs + pop es + + push ax + + ; The emulator requires a few control registers in memory to always be zero for correct + ; instruction decoding (in particular, register look-up operations). These are the + ; emulator's zero segment (ZS) and always-zero flag (XF). Because the emulated memory + ; space is uninitialised, we need to be sure these values are zero before doing anything + ; else. The instructions we need to use to set them must not rely on look-up operations. + ; So e.g. MOV to memory is out but string operations are fine. + + cld + + xor ax, ax + mov di, 24 + stosw ; Set ZS = 0 + mov di, 49 + stosb ; Set XF = 0 + + ; Now we can do whatever we want! DL starts off being the boot disk. + + mov [cs:boot_device], dl + + ; Set up Hercules graphics support. We start with the adapter in text mode + + push dx + + mov dx, 0x3b8 + mov al, 0 + out dx, al ; Set Hercules support to text mode + + mov dx, 0x3b4 + mov al, 1 ; Hercules CRTC "horizontal displayed" register select + out dx, al + mov dx, 0x3b5 + mov al, 0x2d ; 0x2D = 45 (* 16) = 720 pixels wide (GRAPHICS_X) + out dx, al + mov dx, 0x3b4 + mov al, 6 ; Hercules CRTC "vertical displayed" register select + out dx, al + mov dx, 0x3b5 + mov al, 0x57 ; 0x57 = 87 (* 4) = 348 pixels high (GRAPHICS_Y) + out dx, al + + pop dx + + pop ax + + ; Check cold boot/warm boot. We initialise disk parameters on cold boot only + + cmp byte [cs:boot_state], 0 ; Cold boot? + jne boot + + mov byte [cs:boot_state], 1 ; Set flag so next boot will be warm boot + + ; First, set up the disk subsystem. Only do this on the very first startup, when + ; the emulator sets up the CX/AX registers with disk information. + + ; Compute the cylinder/head/sector count for the HD disk image, if present. + ; Total number of sectors is in CX:AX, or 0 if there is no HD image. First, + ; we put it in DX:CX. + + mov dx, cx + mov cx, ax + + mov [cs:hd_secs_hi], dx + mov [cs:hd_secs_lo], cx + + cmp cx, 0 + je maybe_no_hd + + mov word [cs:num_disks], 2 + jmp calc_hd + +maybe_no_hd: + + cmp dx, 0 + je no_hd + + mov word [cs:num_disks], 2 + jmp calc_hd + +no_hd: + + mov word [cs:num_disks], 1 + +calc_hd: + + mov ax, cx + mov word [cs:hd_max_track], 1 + mov word [cs:hd_max_head], 1 + + cmp dx, 0 ; More than 63 total sectors? If so, we have more than 1 track. + ja sect_overflow + cmp ax, 63 + ja sect_overflow + + mov [cs:hd_max_sector], ax + jmp calc_heads + +sect_overflow: + + mov cx, 63 ; Calculate number of tracks + div cx + mov [cs:hd_max_track], ax + mov word [cs:hd_max_sector], 63 + +calc_heads: + + mov dx, 0 ; More than 1024 tracks? If so, we have more than 1 head. + mov ax, [cs:hd_max_track] + cmp ax, 1024 + ja track_overflow + + jmp calc_end + +track_overflow: + + mov cx, 1024 + div cx + mov [cs:hd_max_head], ax + mov word [cs:hd_max_track], 1024 + +calc_end: + + ; Convert number of tracks into maximum track (0-based) and then store in INT 41 + ; HD parameter table + + mov ax, [cs:hd_max_head] + mov [cs:int41_max_heads], al + mov ax, [cs:hd_max_track] + mov [cs:int41_max_cyls], ax + mov ax, [cs:hd_max_sector] + mov [cs:int41_max_sect], al + + dec word [cs:hd_max_track] + dec word [cs:hd_max_head] + +; Main BIOS entry point. Zero the flags, and set up registers. + +boot: mov ax, 0 + push ax + popf + + push cs + push cs + pop ds + pop ss + mov sp, 0xf000 + +; Set up the IVT. First we zero out the table + + cld + + xor ax, ax + mov es, ax + xor di, di + mov cx, 512 + rep stosw + +; Then we load in the pointers to our interrupt handlers + + mov di, 0 + mov si, int_table + mov cx, [itbl_size] + rep movsb + +; Set pointer to INT 41 table for hard disk + + mov cx, int41 + mov word [es:4*0x41], cx + mov cx, 0xf000 + mov word [es:4*0x41 + 2], cx + +; Set up last 16 bytes of memory, including boot jump, BIOS date, machine ID byte + + mov ax, 0xffff + mov es, ax + mov di, 0 + mov si, mem_top + mov cx, 16 + rep movsb + +; Set up the BIOS data area + + mov ax, 0x40 + mov es, ax + mov di, 0 + mov si, bios_data + mov cx, 0x100 + rep movsb + +; Clear video memory + + mov ax, 0xb800 + mov es, ax + mov di, 0 + mov cx, 80*25 + mov ax, 0x0700 + rep stosw + +; Clear video memory shadow buffer + + mov ax, 0xc800 + mov es, ax + mov di, 0 + mov cx, 80*25 + mov ax, 0x0700 + rep stosw + +; Set up some I/O ports, between 0 and FFF. Most of them we set to 0xFF, to indicate no device present + + mov dx, 0x61 + mov al, 0 + out dx, al ; Make sure the speaker is off + + mov dx, 0x60 + out dx, al ; No scancode + + mov dx, 0x64 + out dx, al ; No key waiting + + mov dx, 0 + mov al, 0xFF + +next_out: + + inc dx + + cmp dx, 0x40 ; We deal with the PIT channel 0 later + je next_out + cmp dx, 0x42 ; We deal with the PIT channel 2 later + je next_out + cmp dx, 0x3B8 ; We deal with the Hercules port later, too + je next_out + cmp dx, 0x60 ; Keyboard scancode + je next_out + cmp dx, 0x61 ; Sound output + je next_out + cmp dx, 0x64 ; Keyboard status + je next_out + + out dx, al + + cmp dx, 0xFFF + jl next_out + + mov al, 0 + + mov dx, 0x3DA ; CGA refresh port + out dx, al + + mov dx, 0x3BA ; Hercules detection port + out dx, al + + mov dx, 0x3B8 ; Hercules video mode port + out dx, al + + mov dx, 0x3BC ; LPT1 + out dx, al + + mov dx, 0x62 ; PPI - needed for memory parity checks + out dx, al + +; Get initial RTC value + + push cs + pop es + mov bx, timetable + extended_get_rtc + mov ax, [es:tm_msec] + mov [cs:last_int8_msec], ax + +; Read boot sector from FDD, and load it into 0:7C00 + + mov ax, 0 + mov es, ax + + mov ax, 0x0201 + mov dh, 0 + mov dl, [cs:boot_device] + mov cx, 1 + mov bx, 0x7c00 + int 13h + +; Jump to boot sector + + jmp 0:0x7c00 + +; ************************* INT 7h handler - keyboard driver (8086tiny internal) + +int7: ; Whenever the user presses a key, INT 7 is called by the emulator. + ; ASCII character of the keystroke is at 0040:this_keystroke + + push ds + push es + push ax + push bx + push bp + + push cs + pop ds + + mov bx, 0x40 ; Set segment to BIOS data area segment (0x40) + mov es, bx + + ; Retrieve the keystroke + + mov ax, [es:this_keystroke-bios_data] + mov byte [es:this_keystroke+1-bios_data], 0 + + real_key: + + mov byte [cs:last_key_sdl], 0 + + test ah, 4 ; This key doesn't come from SDL + jz check_linux_bksp + + mov byte [es:keyflags1-bios_data], 0 + mov byte [es:keyflags2-bios_data], 0 + + mov byte [cs:last_key_sdl], 1 ; Key down from SDL + + test ah, 0x40 ; Key up + jz sdl_check_specials + + mov byte [cs:last_key_sdl], 2 ; Key up from SDL + + sdl_check_specials: + + mov bx, ax + and bh, 7 ; If key is between 52F and 534 (Shift/Ctrl/Alt), ignore the key state flags + cmp bx, 0x52f + je sdl_just_press_shift + cmp bx, 0x530 + je sdl_just_press_shift + cmp bx, 0x533 + je sdl_just_press_alt + cmp bx, 0x534 + je sdl_just_press_alt + cmp bx, 0x531 + je sdl_just_press_ctrl + cmp bx, 0x532 + je sdl_just_press_ctrl + jmp sdl_check_alt + + sdl_just_press_shift: + + mov al, 0x36 ; Shift + and ah, 0x40 ; Key up? + add al, ah + add al, ah + call io_key_available + jmp i2_dne + + sdl_just_press_alt: + + mov al, 0x38 ; Alt + and ah, 0x40 ; Key up? + add al, ah + add al, ah + call io_key_available + jmp i2_dne + + sdl_just_press_ctrl: + + mov al, 0x1d ; Ctrl + and ah, 0x40 ; Key up? + add al, ah + add al, ah + call io_key_available + jmp i2_dne + + sdl_check_alt: + + test ah, 8 ; Alt+something? + jz sdl_no_alt + add byte [es:keyflags1-bios_data], 8 + add byte [es:keyflags2-bios_data], 2 + + sdl_no_alt: + + test ah, 0x20 ; Ctrl+something? + jz sdl_no_ctrl + add byte [es:keyflags1-bios_data], 4 + + sdl_no_ctrl: + + test ah, 0x10 ; Shift+something? + jz sdl_no_mods + add byte [es:keyflags1-bios_data], 1 + + sdl_no_mods: + + and ah, 1 ; We have processed all SDL modifiers, so remove them + + ;cmp ax, 160 ; Alt+Space? + ;jne next_sdl_alt_keys + ;mov al, ' ' + ;mov byte [es:this_keystroke-bios_data], al + + check_sdl_f_keys: + + cmp ax, 0x125 + ja i2_dne ; Unknown key + + cmp ax, 0x11a + jb check_sdl_pgup_pgdn_keys + + sub ax, 0xdf ; F1 - F10 + cmp ax, 0x45 + jb check_sdl_f_keys2 + add ax, 0x12 ; F11 - F12 + + check_sdl_f_keys2: + + mov bh, al + mov al, 0 + jmp sdl_scancode_xlat_done + + check_sdl_pgup_pgdn_keys: + + cmp ax, 0x116 + jb check_sdl_cursor_keys + cmp ax, 0x119 + ja check_sdl_cursor_keys + + sub ax, 0x116 + mov bx, pgup_pgdn_xlt + cs xlat + + mov bh, al + mov al, 0 + jmp sdl_scancode_xlat_done + + check_sdl_cursor_keys: + + cmp ax, 0x111 ; SDL cursor keys + jb sdl_process_key ; No special handling for other keys yet + + sub ax, 0x111 + mov bx, unix_cursor_xlt + xlat ; Convert SDL cursor keys to scancode + + mov bh, al + mov al, 0 + mov byte [es:this_keystroke-bios_data], 0 + jmp sdl_scancode_xlat_done + + sdl_process_key: + + cmp ax, 0x100 + jae i2_dne ; Unsupported key + cmp al, 0x7f ; SDL 0x7F backspace? Convert to 0x08 + jne sdl_process_key2 + mov al, 8 + + sdl_process_key2: + + push ax + mov bx, a2scan_tbl ; ASCII to scancode table + xlat + mov bh, al + pop ax ; Scancode in BH, keycode in AL + + sdl_scancode_xlat_done: + + add bh, 0x80 ; Key up scancode + cmp byte [cs:last_key_sdl], 2 ; Key up? + je sdl_not_in_buf + + sub bh, 0x80 ; Key down scancode + + sdl_key_down: + + mov [es:this_keystroke-bios_data], al + + sdl_not_in_buf: + + mov al, bh + call io_key_available + jmp i2_dne + + check_linux_bksp: + + cmp al, 0 ; Null keystroke - ignore + je i2_dne + + cmp al, 0x7f ; Linux code for backspace - change to 8 + jne after_check_bksp + + mov al, 8 + mov byte [es:this_keystroke-bios_data], 8 + + after_check_bksp: + + cmp byte [es:next_key_fn-bios_data], 1 ; If previous keypress was Ctrl+F (signifying this key is is Fxx), skip checks for Ctrl+A (Alt+xx) and Ctrl+F (Fxx) + je i2_n + + cmp al, 0x01 ; Ctrl+A pressed - this is the sequence for "next key is Alt+" + jne i2_not_alt + + mov byte [es:keyflags1-bios_data], 8 ; Alt flag down + mov byte [es:keyflags2-bios_data], 2 ; Alt flag down + mov al, 0x38 ; Simulated Alt by Ctrl+A prefix? + call io_key_available + + mov byte [es:next_key_alt-bios_data], 1 + jmp i2_dne + + i2_not_alt: + + cmp al, 0x06 ; Ctrl+F pressed - this is the sequence for "next key is Fxx" + jne i2_not_fn + + mov byte [es:next_key_fn-bios_data], 1 + jmp i2_dne + + i2_not_fn: + + cmp byte [es:notranslate_flg-bios_data], 1 ; If no translation mode is on, just pass through the scan code. ASCII key is zero. + mov byte [es:notranslate_flg-bios_data], 0 + jne need_to_translate + + mov byte [es:this_keystroke-bios_data], 0 + jmp after_translate + + need_to_translate: + + cmp al, 0xe0 ; Some OSes return scan codes after 0xE0 for things like cursor moves. So, if we find it, set a flag saying the next code received should not be translated. + mov byte [es:notranslate_flg-bios_data], 1 + je i2_dne ; Don't add the 0xE0 to the keyboard buffer + + mov byte [es:notranslate_flg-bios_data], 0 + + cmp al, 0x1b ; ESC key pressed. Either this a "real" escape, or it is UNIX cursor keys. In either case, we do nothing now, except set a flag + jne i2_escnext + + ; If the last key pressed was ESC, then we need to stuff it + cmp byte [es:escape_flag-bios_data], 1 + jne i2_sf + + ; Stuff an ESC character + + mov byte [es:this_keystroke-bios_data], 0x1b + + mov al, 0x01 + call keypress_release + + i2_sf: + + mov byte [es:escape_flag-bios_data], 1 + jmp i2_dne + + i2_escnext: + + ; Check if the last key was an escape character + cmp byte [es:escape_flag-bios_data], 1 + jne i2_noesc + + ; It is, so check if this key is a [ control character + cmp al, '[' ; [ key pressed + je i2_esc + + ; It isn't, so stuff an ESC character plus this key + + mov byte [es:this_keystroke-bios_data], 0x1b + + mov al, 0x01 + call keypress_release + + ; Now actually process this key + mov byte [es:escape_flag-bios_data], 0 + mov al, [es:this_keystroke-bios_data] + jmp i2_noesc + + i2_esc: + + ; Last + this characters are ESC ] - do nothing now, but set escape flag + mov byte [es:escape_flag-bios_data], 2 + jmp i2_dne + + i2_noesc: + + cmp byte [es:escape_flag-bios_data], 2 + jne i2_regular_key + + ; No shifts or Alt for cursor keys + mov byte [es:keyflags1-bios_data], 0 + mov byte [es:keyflags2-bios_data], 0 + + ; Last + this characters are ESC ] xxx - cursor key, so translate and stuff it + sub al, 'A' + mov bx, unix_cursor_xlt + xlat + + mov byte [es:this_keystroke-bios_data], 0 + jmp after_translate + + i2_regular_key: + + mov byte [es:notranslate_flg-bios_data], 0 + + mov bx, a2shift_tbl ; ASCII to shift code table + xlat + + ; Now, BL is 1 if shift is down, 0 otherwise. If shift is down, put a shift down scan code + ; in port 0x60. Then call int 9. Otherwise, put a shift up scan code in, and call int 9. + + push ax + + ; Put shift flags in BIOS, 0040:0017. Add 8 to shift flags if Alt is down. + mov ah, [es:next_key_alt-bios_data] + cpu 186 + shl ah, 3 + cpu 8086 + add al, ah + + cmp byte [es:this_keystroke-bios_data], 0x1A ; Ctrl+A to Ctrl+Z? Then add Ctrl to BIOS key flags + ja i2_no_ctrl + cmp byte [es:this_keystroke-bios_data], 0 + je i2_no_ctrl + cmp byte [es:this_keystroke-bios_data], 0xD ; CR + je i2_no_ctrl + cmp byte [es:this_keystroke-bios_data], 0xA ; LF + je i2_no_ctrl + cmp byte [es:this_keystroke-bios_data], 0x8 ; Backspace + je i2_no_ctrl + cmp byte [es:this_keystroke-bios_data], 0x9 ; Tab + je i2_no_ctrl + add al, 4 ; Ctrl in key flags + + push ax + mov al, 0x1d ; Ctrl key down + call io_key_available + pop ax + + i2_no_ctrl: + + mov [es:keyflags1-bios_data], al + + cpu 186 + shr ah, 2 + cpu 8086 + mov [es:keyflags2-bios_data], ah + + pop ax + + test al, 1 ; Shift down? + jz i2_n + + mov al, 0x36 ; Right shift down + call io_key_available + + i2_n: + + mov al, [es:this_keystroke-bios_data] + + mov bx, a2scan_tbl ; ASCII to scan code table + xlat + + cmp byte [es:next_key_fn-bios_data], 1 ; Fxx? + jne after_translate + + cmp byte [es:this_keystroke-bios_data], 1 ; Ctrl+F then Ctrl+A outputs code for Ctrl+A + je after_translate + + cmp byte [es:this_keystroke-bios_data], 6 ; Ctrl+F then Ctrl+F outputs code for Ctrl+F + je after_translate + + mov byte [es:this_keystroke-bios_data], 0 ; Fxx key, so zero out ASCII code + add al, 0x39 + + after_translate: + + mov byte [es:escape_flag-bios_data], 0 + mov byte [es:escape_flag_last-bios_data], 0 + + ; If the key is actually an Alt+ key we use an ASCII code of 0 instead of the real value. + + cmp byte [es:next_key_alt-bios_data], 1 + jne skip_ascii_zero + + mov byte [es:this_keystroke-bios_data], 0 + + skip_ascii_zero: + + ; Output key down/key up event (scancode in AL) to keyboard port + call keypress_release + + ; If scan code is not 0xE0, then also send right shift up if necessary + cmp al, 0xe0 + je i2_dne + + test byte [es:keyflags1-bios_data], 1 + jz check_ctrl + + mov al, 0xb6 ; Right shift up + call io_key_available + + check_ctrl: + + test byte [es:keyflags1-bios_data], 4 + jz check_alt + + mov al, 0x9d ; Right Ctrl up + call io_key_available + + check_alt: + + mov al, byte [es:next_key_alt-bios_data] + mov byte [es:next_key_alt-bios_data], 0 + mov byte [es:next_key_fn-bios_data], 0 + + cmp al, 1 + je endalt + + jmp i2_dne + + endalt: + + mov al, 0xb8 ; Left Alt up + call io_key_available + + i2_dne: + + pop bp + pop bx + pop ax + pop es + pop ds + iret + +; ************************* INT 9h handler - keyboard (PC BIOS standard) + +int9: + + push es + push ax + push bx + push bp + + in al, 0x60 + + cmp al, 0x80 ; Key up? + jae no_add_buf + cmp al, 0x36 ; Shift? + je no_add_buf + cmp al, 0x38 ; Alt? + je no_add_buf + cmp al, 0x1d ; Ctrl? + je no_add_buf + + mov bx, 0x40 + mov es, bx + + mov bh, al + mov al, [es:this_keystroke-bios_data] + + ; Tail of the BIOS keyboard buffer goes in BP. This is where we add new keystrokes + + mov bp, [es:kbbuf_tail-bios_data] + mov byte [es:bp], al ; ASCII code + mov byte [es:bp+1], bh ; Scan code + + ; ESC keystroke is in the buffer now + add word [es:kbbuf_tail-bios_data], 2 + call kb_adjust_buf ; Wrap the tail around the head if the buffer gets too large + + no_add_buf: + + mov al, 1 + out 0x64, al + + pop bp + pop bx + pop ax + pop es + + iret + +; ************************* INT Ah handler - timer (8086tiny internal) + +inta: + ; 8086tiny called interrupt 0xA frequently, at a rate dependent on the speed of your computer. + ; This interrupt handler scales down the call rate and calls INT 8 at 18.2 times per second, + ; as per a real PC. + + ; See if there is an ESC waiting from a previous INT 7h. If so, put it in the keyboard buffer + ; (because by now - 1/18.2 secs on - we know it can't be part of an escape key sequence). + ; Also handle CGA refresh register. Also release any keys that are still marked as down. + + push ax + push bx + push dx + push bp + push es + + push cx + push di + push ds + push si + + call vmem_driver_entry ; CGA text mode driver - documented later + + ; Increment 32-bit BIOS timer tick counter, once every 18.2 ms + + push cs + pop es + mov bx, timetable + extended_get_rtc + + mov ax, [cs:tm_msec] + sub ax, [cs:last_int8_msec] + + make_ctr_positive: + + cmp ax, 0 + jge no_add_1000 + + add ax, 1000 + jmp make_ctr_positive + + no_add_1000: + + mov bx, 0x40 + mov es, bx + + mov dx, 0 + mov bx, 1193 + mul bx + + mov bx, [es:timer0_freq-bios_data] + + cmp bx, 0 ; 0 actually means FFFF + jne no_adjust_10000 + + mov bx, 0xffff + + no_adjust_10000: + + div bx ; AX now contains number of timer ticks since last int 8 (DX is remainder) + + cmp ax, 0 + je i8_end + + add word [es:0x6C], ax + adc word [es:0x6E], 0 + +inta_call_int8: + + push ax ; Workaround for CPM-86 - INT 1C destroys AX!! + int 8 + pop ax + + dec ax + cmp ax, 0 + jne inta_call_int8 + + mov ax, [cs:tm_msec] + mov [cs:last_int8_msec], ax + +skip_timer_increment: + + ; If last key was from SDL, don't simulate key up events (SDL will do it for us) + cmp byte [cs:last_key_sdl], 0 + jne i8_end + + ; See if we have any keys down. If so, release them + cmp byte [es:key_now_down-bios_data], 0 + je i8_no_key_down + + mov al, [es:key_now_down-bios_data] + mov byte [es:key_now_down-bios_data], 0 + add al, 0x80 + call io_key_available + + i8_no_key_down: + + ; See if we have a waiting ESC flag + cmp byte [es:escape_flag-bios_data], 1 + jne i8_end + + ; Did we have one last two cycles as well? + cmp byte [es:escape_flag_last-bios_data], 1 + je i8_stuff_esc + + inc byte [es:escape_flag_last-bios_data] + jmp i8_end + +i8_stuff_esc: + + ; Yes, clear the ESC flag and put it in the keyboard buffer + mov byte [es:escape_flag-bios_data], 0 + mov byte [es:escape_flag_last-bios_data], 0 + + ; mov bp, [es:kbbuf_tail-bios_data] + ; mov byte [es:bp], 0x1b ; ESC ASCII code + ; mov byte [es:bp+1], 0x01 ; ESC scan code + + ; ESC keystroke is in the buffer now + ; add word [es:kbbuf_tail-bios_data], 2 + ; call kb_adjust_buf ; Wrap the tail around the head if the buffer gets too large + + mov byte [es:this_keystroke-bios_data], 0x1b + + ; Push out ESC keypress/release + mov al, 0x01 + call keypress_release + +i8_end: + + ; A Hercules graphics adapter flips bit 7 of I/O port 3BA on refresh + mov dx, 0x3BA + in al, dx + xor al, 0x80 + out dx, al + + pop si + pop ds + pop di + pop cx + + pop es + pop bp + pop dx + pop bx + pop ax + + iret + +; ************************* INT 8h handler - timer + +int8: + + int 0x1c + iret + +; ************************* INT 10h handler - video services + +int10: + + cmp ah, 0x00 ; Set video mode + je int10_set_vm + cmp ah, 0x01 ; Set cursor shape + je int10_set_cshape + cmp ah, 0x02 ; Set cursor position + je int10_set_cursor + cmp ah, 0x03 ; Get cursur position + je int10_get_cursor + cmp ah, 0x06 ; Scroll up window + je int10_scrollup + cmp ah, 0x07 ; Scroll down window + je int10_scrolldown + cmp ah, 0x08 ; Get character at cursor + je int10_charatcur + cmp ah, 0x09 ; Write char and attribute + je int10_write_char_attrib + cmp ah, 0x0e ; Write character at cursor position + je int10_write_char + cmp ah, 0x0f ; Get video mode + je int10_get_vm + ; cmp ah, 0x1a ; Feature check + ; je int10_features + + iret + + int10_set_vm: + + push dx + push cx + push bx + push es + + cmp al, 4 ; CGA mode 4 + je int10_switch_to_cga_gfx + cmp al, 5 + je int10_switch_to_cga_gfx + cmp al, 6 + je int10_switch_to_cga_gfx + + push ax + + mov dx, 0x3b8 + mov al, 0 + out dx, al + + mov dx, 0x3b4 + mov al, 1 ; Hercules CRTC "horizontal displayed" register select + out dx, al + mov dx, 0x3b5 + mov al, 0x2d ; 0x2D = 45 (* 16) = 720 pixels wide (GRAPHICS_X) + out dx, al + mov dx, 0x3b4 + mov al, 6 ; Hercules CRTC "vertical displayed" register select + out dx, al + mov dx, 0x3b5 + mov al, 0x57 ; 0x57 = 87 (* 4) = 348 pixels high (GRAPHICS_Y) + out dx, al + + mov dx, 0x40 + mov es, dx + + mov byte [es:0xac], 0 ; Tell emulator we are in Hercules mode + + pop ax + + cmp al, 7 ; If an app tries to set Hercules text mode 7, actually set mode 3 (we do not support mode 7's video memory buffer at B000:0) + je int10_set_vm_3 + cmp al, 2 ; Same for text mode 2 (mono) + je int10_set_vm_3 + + jmp int10_set_vm_continue + + int10_switch_to_cga_gfx: + + ; Switch to CGA-like graphics mode (with Hercules CRTC set for 640 x 400) + + mov dx, 0x40 + mov es, dx + + mov [es:0x49], al ; Current video mode + mov byte [es:0xac], 1 ; Tell emulator we are in CGA mode + + mov dx, 0x3b4 + mov al, 1 ; Hercules CRTC "horizontal displayed" register select + out dx, al + mov dx, 0x3b5 + mov al, 0x28 ; 0x28 = 40 (* 16) = 640 pixels wide (GRAPHICS_X) + out dx, al + mov dx, 0x3b4 + mov al, 6 ; Hercules CRTC "vertical displayed" register select + out dx, al + mov dx, 0x3b5 + mov al, 0x64 ; 0x64 = 100 (* 4) = 400 pixels high (GRAPHICS_Y) + out dx, al + + mov dx, 0x3b8 + mov al, 0x8a + out dx, al + + mov bh, 7 + call clear_screen + + mov ax, 0x30 + jmp svmn_exit + + int10_set_vm_3: + + mov al, 3 + + int10_set_vm_continue: + + mov bx, 0x40 + mov es, bx + + mov [es:vidmode-bios_data], al + + mov bh, 7 ; Black background, white foreground + call clear_screen ; ANSI clear screen + + cmp byte [es:vidmode-bios_data], 6 + je set6 + mov al, 0x30 + jmp svmn + + set6: + + mov al, 0x3f + + svmn: + + ; Take Hercules adapter out of graphics mode when resetting video mode via int 10 + push ax + mov dx, 0x3B8 + mov al, 0 + out dx, al + pop ax + + svmn_exit: + + pop es + pop bx + pop cx + pop dx + iret + + int10_set_cshape: + + push ds + push ax + push cx + + mov ax, 0x40 + mov ds, ax + + mov byte [cursor_visible-bios_data], 1 ; Show cursor + + and ch, 01100000b + cmp ch, 00100000b + jne cur_visible + + mov byte [cursor_visible-bios_data], 0 ; Hide cursor + call ansi_hide_cursor + jmp cur_done + + cur_visible: + + call ansi_show_cursor + + cur_done: + + pop cx + pop ax + pop ds + iret + + int10_set_cursor: + + push ds + push ax + + mov ax, 0x40 + mov ds, ax + + mov [curpos_y-bios_data], dh + mov [crt_curpos_y-bios_data], dh + mov [curpos_x-bios_data], dl + mov [crt_curpos_x-bios_data], dl + + cmp dh, 24 + jbe skip_set_cur_row_max + + ; If cursor is moved off the screen, then hide it + call ansi_hide_cursor + jmp skip_set_cur_ansi + + skip_set_cur_row_max: + + cmp dl, 79 + jbe skip_set_cur_col_max + + ; If cursor is moved off the screen, then hide it + call ansi_hide_cursor + jmp skip_set_cur_ansi + + skip_set_cur_col_max: + + mov al, 0x1B ; ANSI + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, dh ; Row number + inc al + call puts_decimal_al + mov al, ';' ; ANSI + extended_putchar_al + mov al, dl ; Column number + inc al + call puts_decimal_al + mov al, 'H' ; Set cursor position command + extended_putchar_al + + cmp byte [cursor_visible-bios_data], 1 + jne skip_set_cur_ansi + call ansi_show_cursor + + skip_set_cur_ansi: + + pop ax + pop ds + iret + + int10_get_cursor: + + push es + + mov cx, 0x40 + mov es, cx + + mov cx, 0x0607 + mov dl, [es:curpos_x-bios_data] + mov dh, [es:curpos_y-bios_data] + + pop es + + iret + + int10_scrollup: + + push bx + push cx + push bp + push ax + + mov bp, bx ; Convert from CGA to ANSI + mov cl, 12 + ror bp, cl + and bp, 7 + mov bl, byte [cs:bp+colour_table] + add bl, 10 + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, bl ; Background colour + call puts_decimal_al + mov al, 'm' ; Set cursor position command + extended_putchar_al + + pop ax + pop bp + pop cx + pop bx + + cmp al, 0 ; Clear window + jne cls_partial + + cmp cx, 0 ; Start of screen + jne cls_partial + + cmp dl, 0x4f ; Clearing columns 0-79 + jb cls_partial + + cmp dh, 0x18 ; Clearing rows 0-24 (or more) + jb cls_partial + + call clear_screen + iret + + cls_partial: + + push ax + push bx + + mov bl, al ; Number of rows to scroll are now in bl + cmp bl, 0 ; Clear whole window? + jne cls_partial_up_whole + + mov bl, 25 ; 25 rows + + cls_partial_up_whole: + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + + cmp ch, 0 ; Start row 1? Maybe full screen + je cls_maybe_fs + jmp cls_not_fs + + cls_maybe_fs: + + cmp dh, 24 ; End row 25? Full screen for sure + je cls_fs + + cls_not_fs: + + mov al, ch ; Start row + inc al + call puts_decimal_al + mov al, ';' ; ANSI + extended_putchar_al + mov al, dh ; End row + inc al + call puts_decimal_al + + cls_fs: + + mov al, 'r' ; Set scrolling window + extended_putchar_al + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + + cmp bl, 1 + jne cls_fs_multiline + + mov al, 'M' + jmp cs_fs_ml_out + +cls_fs_multiline: + + mov al, bl ; Number of rows + call puts_decimal_al + mov al, 'S' ; Scroll up + +cs_fs_ml_out: + + extended_putchar_al + + pop bx + pop ax + + ; Update "actual" cursor position with expected value - different ANSI terminals do different things + ; to the cursor position when you scroll + + push ax + push bx + push dx + push es + + mov ax, 0x40 + mov es, ax + + mov ah, 2 + mov bh, 0 + mov dh, [es:curpos_y-bios_data] + mov dl, [es:curpos_x-bios_data] + int 10h + + pop es + pop dx + pop bx + pop ax + +int10_scroll_up_vmem_update: + + ; Now, we need to update video memory + + push bx + push ax + + push ds + push es + push cx + push dx + push si + push di + + mov byte [cs:vram_dirty], 1 + + push bx + + mov bx, 0xb800 + mov es, bx + mov ds, bx + + pop bx + mov bl, al + + cls_vmem_scroll_up_next_line: + + cmp bl, 0 + je cls_vmem_scroll_up_done + + cls_vmem_scroll_up_one: + + push bx + push dx + + mov ax, 0 + mov al, ch ; Start row number is now in AX + mov bx, 80 + mul bx + add al, cl + adc ah, 0 ; Character number is now in AX + mov bx, 2 + mul bx ; Memory location is now in AX + + pop dx + pop bx + + mov di, ax + mov si, ax + add si, 2*80 ; In a moment we will copy CX words from DS:SI to ES:DI + + mov ax, 0 + add al, dl + adc ah, 0 + inc ax + sub al, cl + sbb ah, 0 ; AX now contains the number of characters from the row to copy + + cmp ch, dh + jae cls_vmem_scroll_up_one_done + +vmem_scroll_up_copy_next_row: + + push cx + mov cx, ax ; CX is now the length (in words) of the row to copy + cld + rep movsw ; Scroll the line up + pop cx + + inc ch ; Move onto the next row + jmp cls_vmem_scroll_up_one + + cls_vmem_scroll_up_one_done: + + push cx + mov cx, ax ; CX is now the length (in words) of the row to copy + mov ah, bh ; Attribute for new line + mov al, 0 ; Write 0 to video memory for new characters + cld + rep stosw + pop cx + + dec bl ; Scroll whole text block another line + jmp cls_vmem_scroll_up_next_line + + cls_vmem_scroll_up_done: + + ;mov al, 0x1B ; Escape + ;extended_putchar_al + ;mov al, '[' ; ANSI + ;extended_putchar_al + ;mov al, '0' ; Reset attributes + ;extended_putchar_al + ;mov al, 'm' + ;extended_putchar_al + + pop di + pop si + pop dx + pop cx + pop es + pop ds + + pop ax + pop bx + + iret + + int10_scrolldown: + + push bx + push cx + push bp + push ax + + mov bp, bx ; Convert from CGA to ANSI + mov cl, 12 + ror bp, cl + and bp, 7 + mov bl, byte [cs:bp+colour_table] + add bl, 10 + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, bl ; Background colour + call puts_decimal_al + mov al, 'm' ; Set cursor position command + extended_putchar_al + + pop ax + pop bp + pop cx + pop bx + + cmp al, 0 ; Clear window + jne cls_partial_down + + cmp cx, 0 ; Start of screen + jne cls_partial_down + + cmp dl, 0x4f ; Clearing columns 0-79 + jne cls_partial_down + + cmp dh, 0x18 ; Clearing rows 0-24 (or more) + jl cls_partial_down + + call clear_screen + iret + + cls_partial_down: + + push ax + push bx + + mov bx, 0 + mov bl, al ; Number of rows to scroll are now in bl + + cmp bl, 0 ; Clear whole window? + jne cls_partial_down_whole + + mov bl, 25 ; 25 rows + + cls_partial_down_whole: + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + + cmp ch, 0 ; Start row 1? Maybe full screen + je cls_maybe_fs_down + jmp cls_not_fs_down + + cls_maybe_fs_down: + + cmp dh, 24 ; End row 25? Full screen for sure + je cls_fs_down + + cls_not_fs_down: + + mov al, ch ; Start row + inc al + call puts_decimal_al + mov al, ';' ; ANSI + extended_putchar_al + mov al, dh ; End row + inc al + call puts_decimal_al + + cls_fs_down: + + mov al, 'r' ; Set scrolling window + extended_putchar_al + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + + cmp bl, 1 + jne cls_fs_down_multiline + + mov al, 'D' + jmp cs_fs_down_ml_out + + cls_fs_down_multiline: + + mov al, bl ; Number of rows + call puts_decimal_al + mov al, 'T' ; Scroll down + + cs_fs_down_ml_out: + + extended_putchar_al + + ; Update "actual" cursor position with expected value - different ANSI terminals do different things + ; to the cursor position when you scroll + + pop bx + pop ax + + push ax + push bx + push dx + push es + + mov ax, 0x40 + mov es, ax + + mov ah, 2 + mov bh, 0 + mov dh, [es:curpos_y-bios_data] + mov dl, [es:curpos_x-bios_data] + int 10h + + pop es + pop dx + pop bx + pop ax + +int10_scroll_down_vmem_update: + + ; Now, we need to update video memory + + push ax + push bx + + push ds + push es + push cx + push dx + push si + push di + + mov byte [cs:vram_dirty], 1 + + push bx + + mov bx, 0xb800 + mov es, bx + mov ds, bx + + pop bx + mov bl, al + + cls_vmem_scroll_down_next_line: + + cmp bl, 0 + je cls_vmem_scroll_down_done + + cls_vmem_scroll_down_one: + + push bx + push dx + + mov ax, 0 + mov al, dh ; End row number is now in AX + mov bx, 80 + mul bx + add al, cl + adc ah, 0 ; Character number is now in AX + mov bx, 2 + mul bx ; Memory location (start of final row) is now in AX + + pop dx + pop bx + + mov di, ax + mov si, ax + sub si, 2*80 ; In a moment we will copy CX words from DS:SI to ES:DI + + mov ax, 0 + add al, dl + adc ah, 0 + inc ax + sub al, cl + sbb ah, 0 ; AX now contains the number of characters from the row to copy + + cmp ch, dh + jae cls_vmem_scroll_down_one_done + + push cx + mov cx, ax ; CX is now the length (in words) of the row to copy + rep movsw ; Scroll the line down + pop cx + + dec dh ; Move onto the next row + jmp cls_vmem_scroll_down_one + + cls_vmem_scroll_down_one_done: + + push cx + mov cx, ax ; CX is now the length (in words) of the row to copy + mov ah, bh ; Attribute for new line + mov al, 0 ; Write 0 to video memory for new characters + rep stosw + pop cx + + dec bl ; Scroll whole text block another line + jmp cls_vmem_scroll_down_next_line + + cls_vmem_scroll_down_done: + + pop di + pop si + pop dx + pop cx + pop es + pop ds + + ;mov al, 0x1B ; Escape + ;extended_putchar_al + ;mov al, '[' ; ANSI + ;extended_putchar_al + ;mov al, '0' ; Reset attributes + ;extended_putchar_al + ;mov al, 'm' + ;extended_putchar_al + + pop bx + pop ax + iret + + int10_charatcur: + + ; This returns the character at the cursor. It is completely dysfunctional, + ; and only works at all if the character has previously been written following + ; an int 10/ah = 2 call to set the cursor position. Added just to support + ; GWBASIC. + + push ds + push es + push bx + push dx + + mov bx, 0x40 + mov es, bx + + mov bx, 0xc000 + mov ds, bx + + mov bx, 160 + mov ax, 0 + mov al, [es:curpos_y-bios_data] + mul bx + + mov bx, 0 + mov bl, [es:curpos_x-bios_data] + add ax, bx + add ax, bx + mov bx, ax + + mov ah, 7 + mov al, [bx] + + pop dx + pop bx + pop es + pop ds + + iret + + i10_unsup: + + iret + + int10_write_char: + + ; First write the character to a buffer at C000:0. This is so that + ; we can later retrieve it using the get character at cursor function, + ; which GWBASIC uses. + + push ds + push es + push cx + push dx + push ax + push bp + push bx + + push ax + + mov cl, al + mov ch, 7 + + mov bx, 0x40 + mov es, bx + + mov bx, 0xc000 + mov ds, bx + + mov bx, 160 + mov ax, 0 + mov al, [es:curpos_y-bios_data] + mul bx + + mov bx, 0 + mov bl, [es:curpos_x-bios_data] + shl bx, 1 + add bx, ax + + mov [bx], cx + + pop ax + push ax + + extended_putchar_al + + jmp int10_write_char_skip_lines + + int10_write_char_attrib: + + ; First write the character to a buffer at C000:0. This is so that + ; we can later retrieve it using the get character at cursor function, + ; which GWBASIC uses. + + push ds + push es + push cx + push dx + push ax + push bp + push bx + + push ax + push cx + + mov cl, al + mov ch, bl + + mov bx, 0x40 + mov es, bx + + mov bx, 0xc000 + mov ds, bx + + mov bx, 160 + mov ax, 0 + mov al, [es:curpos_y-bios_data] + mul bx + + mov bx, 0 + mov bl, [es:curpos_x-bios_data] + shl bx, 1 + add bx, ax + + mov [bx], cx + + mov bl, ch + + mov bh, bl + and bl, 7 ; Foreground colour now in bl + + mov bp, bx ; Convert from CGA to ANSI + and bp, 0xff + mov bl, byte [cs:bp+colour_table] + + and bh, 8 ; Bright attribute now in bh +cpu 186 + shr bh, 3 +cpu 8086 + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, bh ; Bright attribute + call puts_decimal_al + mov al, ';' + extended_putchar_al + mov al, bl ; Foreground colour + call puts_decimal_al + + mov bl, ch + + mov bh, bl +cpu 186 + shr bl, 4 +cpu 8086 + and bl, 7 ; Background colour now in bl + + mov bp, bx ; Convert from CGA to ANSI + and bp, 0xff + mov bl, byte [cs:bp+colour_table] + + add bl, 10 + ; rol bh, 1 + ; and bh, 1 ; Bright attribute now in bh (not used right now) + + mov al, ';' + extended_putchar_al + mov al, bl ; Background colour + call puts_decimal_al + mov al, 'm' ; Set cursor position command + extended_putchar_al + + pop cx + pop ax + push ax + + out_another_char: + + extended_putchar_al + dec cx + cmp cx, 0 + jne out_another_char + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, '0' ; Reset attributes + extended_putchar_al + mov al, 'm' + extended_putchar_al + + int10_write_char_skip_lines: + + pop ax + + push es + pop ds + + cmp al, 0x08 + jne int10_write_char_attrib_inc_x + + dec byte [curpos_x-bios_data] + dec byte [crt_curpos_x-bios_data] + cmp byte [curpos_x-bios_data], 0 + jg int10_write_char_attrib_done + + mov byte [curpos_x-bios_data], 0 + mov byte [crt_curpos_x-bios_data], 0 + jmp int10_write_char_attrib_done + + int10_write_char_attrib_inc_x: + + cmp al, 0x0A ; New line? + je int10_write_char_attrib_newline + + cmp al, 0x0D ; Carriage return? + jne int10_write_char_attrib_not_cr + + mov byte [curpos_x-bios_data], 0 + mov byte [crt_curpos_x-bios_data], 0 + jmp int10_write_char_attrib_done + + int10_write_char_attrib_not_cr: + + inc byte [curpos_x-bios_data] + inc byte [crt_curpos_x-bios_data] + cmp byte [curpos_x-bios_data], 80 + jge int10_write_char_attrib_newline + jmp int10_write_char_attrib_done + + int10_write_char_attrib_newline: + + mov byte [curpos_x-bios_data], 0 + mov byte [crt_curpos_x-bios_data], 0 + inc byte [curpos_y-bios_data] + inc byte [crt_curpos_y-bios_data] + + cmp byte [curpos_y-bios_data], 25 + jb int10_write_char_attrib_done + mov byte [curpos_y-bios_data], 24 + mov byte [crt_curpos_y-bios_data], 24 + + mov bh, 7 + mov al, 1 + mov cx, 0 + mov dx, 0x184f + + pushf + push cs + call int10_scroll_up_vmem_update + + int10_write_char_attrib_done: + + pop bx + pop bp + pop ax + pop dx + pop cx + pop es + pop ds + + iret + + int10_get_vm: + + push es + + mov ax, 0x40 + mov es, ax + + mov ah, 80 ; Number of columns + mov al, [es:vidmode-bios_data] + mov bh, 0 + + pop es + + iret + + int10_features: + + ; Signify we have CGA display + + ; mov al, 0x1a + ; mov bx, 0x0202 + ; iret + +; ************************* INT 11h - get equipment list + +int11: + mov ax, [cs:equip] + iret + +; ************************* INT 12h - return memory size + +int12: + mov ax, 0x280 ; 640K conventional memory + iret + +; ************************* INT 13h handler - disk services + +int13: + cmp ah, 0x00 ; Reset disk + je int13_reset_disk + cmp ah, 0x01 ; Get last status + je int13_last_status + + cmp dl, 0x80 ; Hard disk being queried? + jne i13_diskok + + ; Now, need to check an HD is installed + cmp word [cs:num_disks], 2 + jge i13_diskok + + ; No HD, so return an error + mov ah, 15 ; Report no such drive + jmp reach_stack_stc + + i13_diskok: + + cmp ah, 0x02 ; Read disk + je int13_read_disk + cmp ah, 0x03 ; Write disk + je int13_write_disk + cmp ah, 0x04 ; Verify disk + je int13_verify + cmp ah, 0x05 ; Format track - does nothing here + je int13_format + cmp ah, 0x08 ; Get drive parameters (hard disk) + je int13_getparams + cmp ah, 0x0c ; Seek (hard disk) + je int13_seek + cmp ah, 0x10 ; Check if drive ready (hard disk) + je int13_hdready + cmp ah, 0x15 ; Get disk type + je int13_getdisktype + cmp ah, 0x16 ; Detect disk change + je int13_diskchange + + mov ah, 1 ; Invalid function + jmp reach_stack_stc + + iret + + int13_reset_disk: + + jmp reach_stack_clc + + int13_last_status: + + mov ah, [cs:disk_laststatus] + je ls_no_error + + stc + iret + + ls_no_error: + + clc + iret + + int13_read_disk: + + push dx + + cmp dl, 0 ; Floppy 0 + je i_flop_rd + cmp dl, 0x80 ; HD + je i_hd_rd + + pop dx + mov ah, 1 + jmp reach_stack_stc + + i_flop_rd: + + push si + push bp + + cmp cl, [cs:int1e_spt] + ja rd_error + + pop bp + pop si + + mov dl, 1 ; Floppy disk file handle is stored at j[1] in emulator + jmp i_rd + + i_hd_rd: + + mov dl, 0 ; Hard disk file handle is stored at j[0] in emulator + + i_rd: + + push si + push bp + + ; Convert head/cylinder/sector number to byte offset in disk image + + call chs_to_abs + + ; Now, SI:BP contains the absolute sector offset of the block. We then multiply by 512 to get the offset into the disk image + + mov ah, 0 + cpu 186 + shl ax, 9 + extended_read_disk + shr ax, 9 + cpu 8086 + mov ah, 0x02 ; Put read code back + + cmp al, 0 + je rd_error + + ; Read was successful. Now, check if we have read the boot sector. If so, we want to update + ; our internal table of sectors/track to match the disk format + + cmp dx, 1 ; FDD? + jne rd_noerror + cmp cx, 1 ; First sector? + jne rd_noerror + + push ax + + mov al, [es:bx+24] ; Number of SPT in floppy disk BPB + + ; cmp al, 0 ; If disk is unformatted, do not update the table + ; jne rd_update_spt + cmp al, 9 ; 9 SPT, i.e. 720K disk, so update the table + je rd_update_spt + cmp al, 18 + je rd_update_spt ; 18 SPT, i.e. 1.44MB disk, so update the table + + pop ax + + jmp rd_noerror + + rd_update_spt: + + mov [cs:int1e_spt], al + pop ax + + rd_noerror: + + clc + mov ah, 0 ; No error + jmp rd_finish + + rd_error: + + stc + mov ah, 4 ; Sector not found + + rd_finish: + + pop bp + pop si + pop dx + + mov [cs:disk_laststatus], ah + jmp reach_stack_carry + + int13_write_disk: + + push dx + + cmp dl, 0 ; Floppy 0 + je i_flop_wr + cmp dl, 0x80 ; HD + je i_hd_wr + + pop dx + mov ah, 1 + jmp reach_stack_stc + + i_flop_wr: + + mov dl, 1 ; Floppy disk file handle is stored at j[1] in emulator + jmp i_wr + + i_hd_wr: + + mov dl, 0 ; Hard disk file handle is stored at j[0] in emulator + + i_wr: + + push si + push bp + push cx + push di + + ; Convert head/cylinder/sector number to byte offset in disk image + + call chs_to_abs + + ; Signal an error if we are trying to write beyond the end of the disk + + cmp dl, 0 ; Hard disk? + jne wr_fine ; No - no need for disk sector valid check - NOTE: original submission was JNAE which caused write problems on floppy disk + + ; First, we add the number of sectors we are trying to write from the absolute + ; sector number returned by chs_to_abs. We need to have at least this many + ; sectors on the disk, otherwise return a sector not found error. + + mov cx, bp + mov di, si + + mov ah, 0 + add cx, ax + adc di, 0 + + cmp di, [cs:hd_secs_hi] + ja wr_error + jb wr_fine + cmp cx, [cs:hd_secs_lo] + ja wr_error + +wr_fine: + + mov ah, 0 + cpu 186 + shl ax, 9 + extended_write_disk + shr ax, 9 + cpu 8086 + mov ah, 0x03 ; Put write code back + + cmp al, 0 + je wr_error + + clc + mov ah, 0 ; No error + jmp wr_finish + + wr_error: + + stc + mov ah, 4 ; Sector not found + + wr_finish: + + pop di + pop cx + pop bp + pop si + pop dx + + mov [cs:disk_laststatus], ah + jmp reach_stack_carry + + int13_verify: + + mov ah, 0 + jmp reach_stack_clc + + int13_getparams: + + cmp dl, 0 + je i_gp_fl + cmp dl, 0x80 + je i_gp_hd + + mov ah, 0x01 + mov [cs:disk_laststatus], ah + jmp reach_stack_stc + + i_gp_fl: + + push cs + pop es + mov di, int1e ; ES:DI now points to floppy parameters table (INT 1E) + + mov ax, 0 + mov bx, 4 + mov ch, 0x4f + mov cl, [cs:int1e_spt] + mov dx, 0x0101 + + mov byte [cs:disk_laststatus], 0 + jmp reach_stack_clc + + i_gp_hd: + + mov ax, 0 + mov bx, 0 + mov dl, 1 + mov dh, [cs:hd_max_head] + mov cx, [cs:hd_max_track] + ror ch, 1 + ror ch, 1 + add ch, [cs:hd_max_sector] + xchg ch, cl + + mov byte [cs:disk_laststatus], 0 + jmp reach_stack_clc + + int13_seek: + + mov ah, 0 + jmp reach_stack_clc + + int13_hdready: + + cmp byte [cs:num_disks], 2 ; HD present? + jne int13_hdready_nohd + cmp dl, 0x80 ; Checking first HD? + jne int13_hdready_nohd + + mov ah, 0 + jmp reach_stack_clc + + int13_hdready_nohd: + + jmp reach_stack_stc + + int13_format: + + mov ah, 0 + jmp reach_stack_clc + + int13_getdisktype: + + cmp dl, 0 ; Floppy + je gdt_flop + cmp dl, 0x80 ; HD + je gdt_hd + + mov ah, 15 ; Report no such drive + mov [cs:disk_laststatus], ah + jmp reach_stack_stc + + gdt_flop: + + mov ah, 1 + jmp reach_stack_clc + + gdt_hd: + + mov ah, 3 + mov cx, [cs:hd_secs_hi] + mov dx, [cs:hd_secs_lo] + jmp reach_stack_clc + + int13_diskchange: + + mov ah, 0 ; Disk not changed + jmp reach_stack_clc + +; ************************* INT 14h - serial port functions + +int14: + cmp ah, 0 + je int14_init + + jmp reach_stack_stc + + int14_init: + + mov ax, 0 + jmp reach_stack_stc + +; ************************* INT 15h - get system configuration + +int15: ; Here we do not support any of the functions, and just return + ; a function not supported code - like the original IBM PC/XT does. + + ; cmp ah, 0xc0 + ; je int15_sysconfig + ; cmp ah, 0x41 + ; je int15_waitevent + ; cmp ah, 0x4f + ; je int15_intercept + ; cmp ah, 0x88 + ; je int15_getextmem + +; Otherwise, function not supported + + mov ah, 0x86 + + jmp reach_stack_stc + +; int15_sysconfig: ; Return address of system configuration table in ROM +; +; mov bx, 0xf000 +; mov es, bx +; mov bx, rom_config +; mov ah, 0 +; +; jmp reach_stack_clc +; +; int15_waitevent: ; Events not supported +; +; mov ah, 0x86 +; +; jmp reach_stack_stc +; +; int15_intercept: ; Keyboard intercept +; +; jmp reach_stack_stc +; +; int15_getextmem: ; Extended memory not supported +; +; mov ah,0x86 +; +; jmp reach_stack_stc + +; ************************* INT 16h handler - keyboard + +int16: + cmp ah, 0x00 ; Get keystroke (remove from buffer) + je kb_getkey + cmp ah, 0x01 ; Check for keystroke (do not remove from buffer) + je kb_checkkey + cmp ah, 0x02 ; Check shift flags + je kb_shiftflags + cmp ah, 0x12 ; Check shift flags + je kb_extshiftflags + + iret + + kb_getkey: + + push es + push bx + push cx + push dx + + mov bx, 0x40 + mov es, bx + + kb_gkblock: + + cli + + mov cx, [es:kbbuf_tail-bios_data] + mov bx, [es:kbbuf_head-bios_data] + mov dx, [es:bx] + + sti + + ; Wait until there is a key in the buffer + cmp cx, bx + je kb_gkblock + + add word [es:kbbuf_head-bios_data], 2 + call kb_adjust_buf + + mov ah, dh + mov al, dl + + pop dx + pop cx + pop bx + pop es + + iret + + kb_checkkey: + + push es + push bx + push cx + push dx + + mov bx, 0x40 + mov es, bx + + mov cx, [es:kbbuf_tail-bios_data] + mov bx, [es:kbbuf_head-bios_data] + mov dx, [es:bx] + + sti + + ; Check if there is a key in the buffer. ZF is set if there is none. + cmp cx, bx + + mov ah, dh + mov al, dl + + pop dx + pop cx + pop bx + pop es + + retf 2 ; NEED TO FIX THIS!! + + kb_shiftflags: + + push es + push bx + + mov bx, 0x40 + mov es, bx + + mov al, [es:keyflags1-bios_data] + + pop bx + pop es + + iret + + kb_extshiftflags: + + push es + push bx + + mov bx, 0x40 + mov es, bx + + mov al, [es:keyflags1-bios_data] + mov ah, al + + pop bx + pop es + + iret + +; ************************* INT 17h handler - printer + +int17: + cmp ah, 0x01 + je int17_initprint ; Initialise printer + + jmp reach_stack_stc + + int17_initprint: + + mov ah, 1 ; No printer + jmp reach_stack_stc + +; ************************* INT 19h = reboot + +int19: + jmp boot + +; ************************* INT 1Ah - clock + +int1a: + cmp ah, 0 + je int1a_getsystime ; Get ticks since midnight (used for RTC time) + cmp ah, 2 + je int1a_gettime ; Get RTC time (not actually used by DOS) + cmp ah, 4 + je int1a_getdate ; Get RTC date + cmp ah, 0x0f + je int1a_init ; Initialise RTC + + iret + + int1a_getsystime: + + push ax + push bx + push ds + push es + + push cs + push cs + pop ds + pop es + + mov bx, timetable + + extended_get_rtc + + mov ax, 182 ; Clock ticks in 10 seconds + mul word [tm_msec] + mov bx, 10000 + div bx ; AX now contains clock ticks in milliseconds counter + mov [tm_msec], ax + + mov ax, 182 ; Clock ticks in 10 seconds + mul word [tm_sec] + mov bx, 10 + mov dx, 0 + div bx ; AX now contains clock ticks in seconds counter + mov [tm_sec], ax + + mov ax, 1092 ; Clock ticks in a minute + mul word [tm_min] ; AX now contains clock ticks in minutes counter + mov [tm_min], ax + + mov ax, 65520 ; Clock ticks in an hour + mul word [tm_hour] ; DX:AX now contains clock ticks in hours counter + + add ax, [tm_msec] ; Add milliseconds in to AX + adc dx, 0 ; Carry into DX if necessary + add ax, [tm_sec] ; Add seconds in to AX + adc dx, 0 ; Carry into DX if necessary + add ax, [tm_min] ; Add minutes in to AX + adc dx, 0 ; Carry into DX if necessary + + push dx + push ax + pop dx + pop cx + + pop es + pop ds + pop bx + pop ax + + mov al, 0 + iret + + int1a_gettime: + + ; Return the system time in BCD format. DOS doesn't use this, but we need to return + ; something or the system thinks there is no RTC. + + push ds + push es + push ax + push bx + + push cs + push cs + pop ds + pop es + + mov bx, timetable + + extended_get_rtc + + mov ax, 0 + mov cx, [tm_hour] + call hex_to_bcd + mov bh, al ; Hour in BCD is in BH + + mov ax, 0 + mov cx, [tm_min] + call hex_to_bcd + mov bl, al ; Minute in BCD is in BL + + mov ax, 0 + mov cx, [tm_sec] + call hex_to_bcd + mov dh, al ; Second in BCD is in DH + + mov dl, 0 ; Daylight saving flag = 0 always + + mov cx, bx ; Hour:minute now in CH:CL + + pop bx + pop ax + pop es + pop ds + + jmp reach_stack_clc + + int1a_getdate: + + ; Return the system date in BCD format. + + push ds + push es + push bx + push ax + + push cs + push cs + pop ds + pop es + + mov bx, timetable + + extended_get_rtc + + mov ax, 0x1900 + mov cx, [tm_year] + call hex_to_bcd + mov cx, ax + push cx + + mov ax, 1 + mov cx, [tm_mon] + call hex_to_bcd + mov dh, al + + mov ax, 0 + mov cx, [tm_mday] + call hex_to_bcd + mov dl, al + + pop cx + pop ax + pop bx + pop es + pop ds + + jmp reach_stack_clc + + int1a_init: + + jmp reach_stack_clc + +; ************************* INT 1Ch - the other timer interrupt + +int1c: + + iret + +; ************************* INT 1Eh - diskette parameter table + +int1e: + + db 0xdf ; Step rate 2ms, head unload time 240ms + db 0x02 ; Head load time 4 ms, non-DMA mode 0 + db 0x25 ; Byte delay until motor turned off + db 0x02 ; 512 bytes per sector +int1e_spt db 18 ; 18 sectors per track (1.44MB) + db 0x1B ; Gap between sectors for 3.5" floppy + db 0xFF ; Data length (ignored) + db 0x54 ; Gap length when formatting + db 0xF6 ; Format filler byte + db 0x0F ; Head settle time (1 ms) + db 0x08 ; Motor start time in 1/8 seconds + +; ************************* INT 41h - hard disk parameter table + +int41: + +int41_max_cyls dw 0 +int41_max_heads db 0 + dw 0 + dw 0 + db 0 + db 11000000b + db 0 + db 0 + db 0 + dw 0 +int41_max_sect db 0 + db 0 + +; ************************* ROM configuration table + +rom_config dw 16 ; 16 bytes following + db 0xfe ; Model + db 'A' ; Submodel + db 'C' ; BIOS revision + db 0b00100000 ; Feature 1 + db 0b00000000 ; Feature 2 + db 0b00000000 ; Feature 3 + db 0b00000000 ; Feature 4 + db 0b00000000 ; Feature 5 + db 0, 0, 0, 0, 0, 0 + +; Internal state variables + +num_disks dw 0 ; Number of disks present +hd_secs_hi dw 0 ; Total sectors on HD (high word) +hd_secs_lo dw 0 ; Total sectors on HD (low word) +hd_max_sector dw 0 ; Max sector number on HD +hd_max_track dw 0 ; Max track number on HD +hd_max_head dw 0 ; Max head number on HD +drive_tracks_temp dw 0 +drive_sectors_temp dw 0 +drive_heads_temp dw 0 +drive_num_temp dw 0 +boot_state db 0 +cga_refresh_reg db 0 + +; Default interrupt handlers + +int0: +int1: +int2: +int3: +int4: +int5: +int6: +intb: +intc: +intd: +inte: +intf: +int18: +int1b: +int1d: + +iret + +; ************ Function call library ************ + +; Hex to BCD routine. Input is AX in hex (can be 0), and adds CX in hex to it, forming a BCD output in AX. + +hex_to_bcd: + + push bx + + jcxz h2bfin + + h2bloop: + + inc ax + + ; First process the low nibble of AL + mov bh, al + and bh, 0x0f + cmp bh, 0x0a + jne c1 + add ax, 0x0006 + + ; Then the high nibble of AL + c1: + mov bh, al + and bh, 0xf0 + cmp bh, 0xa0 + jne c2 + add ax, 0x0060 + + ; Then the low nibble of AH + c2: + mov bh, ah + and bh, 0x0f + cmp bh, 0x0a + jne c3 + add ax, 0x0600 + + c3: + loop h2bloop + h2bfin: + pop bx + ret + +; Takes a number in AL (from 0 to 99), and outputs the value in decimal using extended_putchar_al. + +puts_decimal_al: + + push ax + + aam + add ax, 0x3030 ; '00' + + cmp ah, 0x30 + je pda_2nd ; First digit is zero, so print only 2nd digit + + xchg ah, al ; First digit is now in AL + extended_putchar_al ; Print first digit + xchg ah, al ; Second digit is now in AL + + pda_2nd: + + extended_putchar_al ; Print second digit + + pop ax + ret + +; Keyboard adjust buffer head and tail. If either head or the tail are at the end of the buffer, reset them +; back to the start, since it is a circular buffer. + +kb_adjust_buf: + + push ax + push bx + + ; Check to see if the head is at the end of the buffer (or beyond). If so, bring it back + ; to the start + + mov ax, [es:kbbuf_end_ptr-bios_data] + cmp [es:kbbuf_head-bios_data], ax + jnge kb_adjust_tail + + mov bx, [es:kbbuf_start_ptr-bios_data] + mov [es:kbbuf_head-bios_data], bx + + kb_adjust_tail: + + ; Check to see if the tail is at the end of the buffer (or beyond). If so, bring it back + ; to the start + + mov ax, [es:kbbuf_end_ptr-bios_data] + cmp [es:kbbuf_tail-bios_data], ax + jnge kb_adjust_done + + mov bx, [es:kbbuf_start_ptr-bios_data] + mov [es:kbbuf_tail-bios_data], bx + + kb_adjust_done: + + pop bx + pop ax + ret + +; Convert CHS disk position (in CH, CL and DH) to absolute sector number in BP:SI +; Floppy disks have 512 bytes per sector, 9/18 sectors per track, 2 heads. DH is head number (1 or 0), CH bits 5..0 is +; sector number, CL7..6 + CH7..0 is 10-bit cylinder/track number. Hard disks have 512 bytes per sector, but a variable +; number of tracks and heads. + +chs_to_abs: + + push ax + push bx + push cx + push dx + + mov [cs:drive_num_temp], dl + + ; First, we extract the track number from CH and CL. + + push cx + mov bh, cl + mov cl, 6 + shr bh, cl + mov bl, ch + + ; Multiply track number (now in BX) by the number of heads + + cmp byte [cs:drive_num_temp], 1 ; Floppy disk? + + push dx + + mov dx, 0 + xchg ax, bx + + jne chs_hd + + shl ax, 1 ; Multiply by 2 (number of heads on FD) + push ax + xor ax, ax + mov al, [cs:int1e_spt] + mov [cs:drive_sectors_temp], ax ; Retrieve sectors per track from INT 1E table + pop ax + + jmp chs_continue + +chs_hd: + + mov bp, [cs:hd_max_head] + inc bp + mov [cs:drive_heads_temp], bp + + mul word [cs:drive_heads_temp] ; HD, so multiply by computed head count + + mov bp, [cs:hd_max_sector] ; We previously calculated maximum HD track, so number of tracks is 1 more + mov [cs:drive_sectors_temp], bp + +chs_continue: + + xchg ax, bx + + pop dx + + xchg dh, dl + mov dh, 0 + add bx, dx + + mov ax, [cs:drive_sectors_temp] + mul bx + + ; Now we extract the sector number (from 1 to 63) - for some reason they start from 1 + + pop cx + mov ch, 0 + and cl, 0x3F + dec cl + + add ax, cx + adc dx, 0 + mov bp, ax + mov si, dx + + ; Now, SI:BP contains offset into disk image file (FD or HD) + + pop dx + pop cx + pop bx + pop ax + ret + +; Clear screen using ANSI codes. Also clear video memory with attribute in BH + +clear_screen: + + push ax + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, 'r' ; Set scrolling window + extended_putchar_al + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, '0' ; Reset attributes + extended_putchar_al + mov al, 'm' ; Reset attributes + extended_putchar_al + + push bx + push cx + push bp + push ax + push es + + mov bp, bx ; Convert from CGA to ANSI + mov cl, 12 + ror bp, cl + and bp, 7 + mov bl, byte [cs:bp+colour_table] + add bl, 10 + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, bl ; Background colour + call puts_decimal_al + mov al, 'm' ; Set cursor position command + extended_putchar_al + + mov ax, 0x40 + mov es, ax + mov byte [es:curpos_x-bios_data], 0 + mov byte [es:crt_curpos_x-bios_data], 0 + mov byte [es:curpos_y-bios_data], 0 + mov byte [es:crt_curpos_y-bios_data], 0 + + pop es + pop ax + pop bp + pop cx + pop bx + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, '2' ; Clear screen + extended_putchar_al + mov al, 'J' + extended_putchar_al + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, '1' ; Cursor row 1 + extended_putchar_al + mov al, ';' + extended_putchar_al + mov al, '1' ; Cursor column 1 + extended_putchar_al + mov al, 'H' ; Set cursor + extended_putchar_al + + push es + push di + push cx + + cld + mov ax, 0xb800 + mov es, ax + mov di, 0 + mov al, 0 + mov ah, bh + mov cx, 80*25 + rep stosw + + cld + mov di, 0xc800 + mov es, di + mov di, 0 + mov cx, 80*25 + rep stosw + + cld + mov di, 0xc000 + mov es, di + mov di, 0 + mov cx, 80*25 + rep stosw + + pop cx + pop di + pop es + + pop ax + + mov byte [cs:vram_dirty], 1 + + ret + +; Pushes a key press, followed by a key release, event to I/O port 0x60 and calls +; INT 9. + +keypress_release: + + push ax + + cmp byte [es:key_now_down-bios_data], 0 + je kpr_no_prev_release + + mov al, [es:key_now_down-bios_data] + add al, 0x80 + call io_key_available + + pop ax + push ax + + kpr_no_prev_release: + + mov [es:key_now_down-bios_data], al + call io_key_available + + pop ax + + ret + +; Sets key available flag on I/O port 0x64, outputs key scan code in AL to I/O port 0x60, and calls INT 9 + +io_key_available: + + push ax + mov al, 1 + out 0x64, al + pop ax + + out 0x60, al + int 9 + ret + +; Reaches up into the stack before the end of an interrupt handler, and sets the carry flag + +reach_stack_stc: + + xchg bp, sp + or word [bp+4], 1 + xchg bp, sp + iret + +; Reaches up into the stack before the end of an interrupt handler, and clears the carry flag + +reach_stack_clc: + + xchg bp, sp + and word [bp+4], 0xfffe + xchg bp, sp + iret + +; Reaches up into the stack before the end of an interrupt handler, and returns with the current +; setting of the carry flag + +reach_stack_carry: + + jc reach_stack_stc + jmp reach_stack_clc + +; This is the VMEM driver, to support direct video memory access in 80x25 colour CGA mode. +; It scans through CGA video memory at address B800:0, and if there is anything there (i.e. +; applications are doing direct video memory writes), converts the buffer to a sequence of +; ANSI terminal codes to render the screen output. +; +; Note: this destroys all registers. It is the responsibility of the caller to save/restore +; them. + +vmem_driver_entry: + + cmp byte [cs:in_update], 1 + je just_finish ; If we are already in the middle of an update, skip. Needed for re-entrancy + + inc byte [cs:int8_ctr] + cmp byte [cs:int8_ctr], 8 ; Only do this once every 8 timer ticks + jne just_finish + +gmode_test: + + mov byte [cs:int8_ctr], 0 + mov dx, 0x3b8 ; Do not update if in Hercules graphics mode + in al, dx + test al, 2 + jz vram_zero_check + +just_finish: + + ret + +vram_zero_check: ; Check if video memory is blank - if so, do nothing + + mov byte [cs:in_update], 1 + + sti + + mov bx, 0x40 + mov ds, bx + + mov di, [vmem_offset-bios_data] ; Adjust for CRTC video memory offset register + shl di, 1 + push di + + mov bx, 0xb800 + mov es, bx + mov cx, 0x7d0 + mov ax, 0x0700 + + cld + repz scasw + pop di + je vmem_done ; Nothing has been written to video RAM - no need to update + + cmp byte [cs:vram_dirty], 1 ; Cleared screen so always need to update + je vram_update + + mov bx, 0xc800 + mov ds, bx + mov si, 0 + mov cx, 0x7d0 + + cld + repz cmpsw + jne vram_update ; Video RAM is changed - need to update + + mov bx, 0x40 + mov ds, bx + mov bh, [crt_curpos_y-bios_data] + mov bl, [crt_curpos_x-bios_data] + + cmp bh, [cs:crt_curpos_y_last] + jne restore_cursor ; Cursor position changed (but nothing else) so update just that + cmp bl, [cs:crt_curpos_x_last] + jne restore_cursor + + jmp vmem_done + +vram_update: + + mov bx, 0x40 + mov es, bx + + push cs + pop ds + + mov byte [int_curpos_x], 0xff + mov byte [int_curpos_y], 0xff + + cmp byte [es:cursor_visible-bios_data], 0 + je dont_hide_cursor + + call ansi_hide_cursor + +dont_hide_cursor: + + mov byte [last_attrib], 0xff + + mov bx, 0x40 + mov es, bx + + mov di, [es:vmem_offset-bios_data] ; Adjust for CRTC video memory offset register + shl di, 1 + sub di, 2 ; Combined offset + + mov bx, 0xb800 + mov es, bx + + ; Set up the initial cursor coordinates. Since the first thing we do is increment the cursor + ; position, this initial position is actually off the screen + + mov bp, -1 ; Row number + mov si, 79 ; Column number + +disp_loop: + + ; Advance to next column + + add di, 2 + inc si + cmp si, 80 + jne cont + + ; Column is 80, so set to 0 and advance a line + +loop_next_line: + + mov si, 0 + inc bp + + ; Bottom of the screen reached already? If so, we're done + + cmp bp, 25 + je restore_attrib + + ; See if this line has changed in video RAM + + cmp byte [cs:vram_dirty], 1 + je cont + + push si + push di + + mov bx, 0xb800 + mov ds, bx + mov bx, 0xc800 + mov es, bx + mov si, di + + push es + mov bx, 0x40 + mov es, bx + sub di, [es:vmem_offset-bios_data] ; Adjust for CRTC video memory offset register + sub di, [es:vmem_offset-bios_data] + pop es + + mov cx, 80 ; One row's worth of characters + + cld + repz cmpsw + pop di + pop si + + je vmem_next_line ; This line is unchanged in video RAM, so do not update + +vmem_copy_buf: + + ; Copy the changed line to our double buffer at C800:0 + + push cx + push si + push di + + push es + mov bx, 0x40 + mov es, bx + mov si, di + sub di, [es:vmem_offset-bios_data] ; Adjust for CRTC video memory offset register + sub di, [es:vmem_offset-bios_data] + pop es + + mov cx, 80 ; One row's worth of characters + cld + rep movsw + + pop di + pop si + pop cx + + ; We want to start the update at the first character which differs - so calculate its position. + + mov bx, 79 + sub bx, cx + + add di, bx + add di, bx + add si, bx + + push ds + pop es ; Set ES back to B800 + + jmp cont + +vmem_next_line: + + add di, 160 + jmp loop_next_line ; Line is unchanged in video RAM + +cont: + push cs + pop ds + + cmp byte [es:di], 0 ; Ignore null characters in video memory + je disp_loop + + mov ax, bp + mov bx, si + mov dh, al + mov dl, bl + + cmp dh, [int_curpos_y] ; Same row as the last time? + jne ansi_set_cur_pos + push dx + dec dl + cmp dl, [int_curpos_x] ; One column to the right since the last time? + pop dx + je skip_set_cur_pos + +ansi_set_cur_pos: + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, dh ; Row number + inc al + call puts_decimal_al + mov al, ';' ; ANSI + extended_putchar_al + mov al, dl ; Column number + inc al + call puts_decimal_al + mov al, 'H' ; Set cursor position command + extended_putchar_al + + mov [int_curpos_y], dh + +skip_set_cur_pos: + + mov [int_curpos_x], dl + + mov dl, [es:di+1] + cmp dl, [last_attrib] + je skip_attrib + + mov [last_attrib], dl + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + + mov al, dl + and al, 8 ; Bright attribute now in AL + cpu 186 + shr al, 3 + cpu 8086 + + call puts_decimal_al + mov al, ';' + extended_putchar_al + + push dx + + and dl, 7 ; Foreground colour now in DL + mov bx, colour_table + mov al, dl + xlat + + call puts_decimal_al + mov al, ';' + extended_putchar_al + + pop dx + + cpu 186 + shr dl, 4 + cpu 8086 + and dl, 7 ; Background colour now in DL + + mov al, dl + xlat + + add al, 10 + call puts_decimal_al + mov al, 'm' ; Set cursor attribute command + extended_putchar_al + +skip_attrib: + + mov al, [es:di] + + cmp al, 32 ; Non-printable ASCII? (< 32 decimal) + jae just_show_it + + mov bx, low_ascii_conv + cs xlat ; Convert to printable representation (mostly spaces) + +just_show_it: + + extended_putchar_al + + jmp disp_loop + +restore_attrib: + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, '0' ; Reset attributes + extended_putchar_al + mov al, 'm' + extended_putchar_al + +restore_cursor: + + ; On a real PC, the 6845 CRT cursor position registers take place over the BIOS + ; Data Area ones. So, if the cursor is not off the screen, set it to the CRT + ; position. + + mov bx, 0x40 + mov ds, bx + + mov bh, [crt_curpos_y-bios_data] + mov bl, [crt_curpos_x-bios_data] + mov [cs:crt_curpos_y_last], bh + mov [cs:crt_curpos_x_last], bl + + cmp bh, 24 + ja vmem_end_hidden_cursor + cmp bl, 79 + ja vmem_end_hidden_cursor + + mov al, 0x1B ; ANSI + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, bh ; Row number + inc al + call puts_decimal_al + mov al, ';' ; ANSI + extended_putchar_al + mov al, bl ; Column number + inc al + call puts_decimal_al + mov al, 'H' ; Set cursor position command + extended_putchar_al + +restore_cursor_visible: + + cmp byte [cursor_visible-bios_data], 1 + jne vmem_end_hidden_cursor + + call ansi_show_cursor + jmp vmem_done + +vmem_end_hidden_cursor: + + call ansi_hide_cursor + +vmem_done: + + mov byte [cs:vram_dirty], 0 + mov byte [cs:in_update], 0 + ret + +; Show cursor using ANSI codes + +ansi_show_cursor: + + mov al, 0x1B + extended_putchar_al + mov al, '[' + extended_putchar_al + mov al, '?' + extended_putchar_al + mov al, '2' + extended_putchar_al + mov al, '5' + extended_putchar_al + mov al, 'h' + extended_putchar_al + + ret + +; Hide cursor using ANSI codes + +ansi_hide_cursor: + + mov al, 0x1B + extended_putchar_al + mov al, '[' + extended_putchar_al + mov al, '?' + extended_putchar_al + mov al, '2' + extended_putchar_al + mov al, '5' + extended_putchar_al + mov al, 'l' + extended_putchar_al + + ret + +; **************************************************************************************** +; That's it for the code. Now, the data tables follow. +; **************************************************************************************** + +; Standard PC-compatible BIOS data area - to copy to 40:0 + +bios_data: + +com1addr dw 0 +com2addr dw 0 +com3addr dw 0 +com4addr dw 0 +lpt1addr dw 0 +lpt2addr dw 0 +lpt3addr dw 0 +lpt4addr dw 0 +equip dw 0b0000000000100001 +;equip dw 0b0000000100100001 + db 0 +memsize dw 0x280 + db 0 + db 0 +keyflags1 db 0 +keyflags2 db 0 + db 0 +kbbuf_head dw kbbuf-bios_data +kbbuf_tail dw kbbuf-bios_data +kbbuf: times 32 db 'X' +drivecal db 0 +diskmotor db 0 +motorshutoff db 0x07 +disk_laststatus db 0 +times 7 db 0 +vidmode db 0x03 +vid_cols dw 80 +page_size dw 0x1000 + dw 0 +curpos_x db 0 +curpos_y db 0 +times 7 dw 0 +cur_v_end db 7 +cur_v_start db 6 +disp_page db 0 +crtport dw 0x3d4 + db 10 + db 0 +times 5 db 0 +clk_dtimer dd 0 +clk_rollover db 0 +ctrl_break db 0 +soft_rst_flg dw 0x1234 + db 0 +num_hd db 0 + db 0 + db 0 + dd 0 + dd 0 +kbbuf_start_ptr dw 0x001e +kbbuf_end_ptr dw 0x003e +vid_rows db 25 ; at 40:84 + db 0 + db 0 +vidmode_opt db 0 ; 0x70 + db 0 ; 0x89 + db 0 ; 0x51 + db 0 ; 0x0c + db 0 + db 0 + db 0 + db 0 + db 0 + db 0 + db 0 + db 0 + db 0 + db 0 + db 0 +kb_mode db 0 +kb_led db 0 + db 0 + db 0 + db 0 + db 0 +boot_device db 0 +crt_curpos_x db 0 +crt_curpos_y db 0 +key_now_down db 0 +next_key_fn db 0 +cursor_visible db 1 +escape_flag_last db 0 +next_key_alt db 0 +escape_flag db 0 +notranslate_flg db 0 +this_keystroke db 0 +this_keystroke_ext db 0 +timer0_freq dw 0xffff ; PIT channel 0 (55ms) +timer2_freq dw 0 ; PIT channel 2 +cga_vmode db 0 +vmem_offset dw 0 ; Video RAM offset +ending: times (0xff-($-com1addr)) db 0 + +; Keyboard scan code tables + +a2scan_tbl db 0xFF, 0x1E, 0x30, 0x2E, 0x20, 0x12, 0x21, 0x22, 0x0E, 0x0F, 0x24, 0x25, 0x26, 0x1C, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1F, 0x14, 0x16, 0x2F, 0x11, 0x2D, 0x15, 0x2C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x39, 0x02, 0x28, 0x04, 0x05, 0x06, 0x08, 0x28, 0x0A, 0x0B, 0x09, 0x0D, 0x33, 0x0C, 0x34, 0x35, 0x0B, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x27, 0x27, 0x33, 0x0D, 0x34, 0x35, 0x03, 0x1E, 0x30, 0x2E, 0x20, 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, 0x25, 0x26, 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1F, 0x14, 0x16, 0x2F, 0x11, 0x2D, 0x15, 0x2C, 0x1A, 0x2B, 0x1B, 0x07, 0x0C, 0x29, 0x1E, 0x30, 0x2E, 0x20, 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, 0x25, 0x26, 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1F, 0x14, 0x16, 0x2F, 0x11, 0x2D, 0x15, 0x2C, 0x1A, 0x2B, 0x1B, 0x29, 0x0E +a2shift_tbl db 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, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 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, 1, 1, 1, 1, 0 + +; Interrupt vector table - to copy to 0:0 + +int_table dw int0 + dw 0xf000 + dw int1 + dw 0xf000 + dw int2 + dw 0xf000 + dw int3 + dw 0xf000 + dw int4 + dw 0xf000 + dw int5 + dw 0xf000 + dw int6 + dw 0xf000 + dw int7 + dw 0xf000 + dw int8 + dw 0xf000 + dw int9 + dw 0xf000 + dw inta + dw 0xf000 + dw intb + dw 0xf000 + dw intc + dw 0xf000 + dw intd + dw 0xf000 + dw inte + dw 0xf000 + dw intf + dw 0xf000 + dw int10 + dw 0xf000 + dw int11 + dw 0xf000 + dw int12 + dw 0xf000 + dw int13 + dw 0xf000 + dw int14 + dw 0xf000 + dw int15 + dw 0xf000 + dw int16 + dw 0xf000 + dw int17 + dw 0xf000 + dw int18 + dw 0xf000 + dw int19 + dw 0xf000 + dw int1a + dw 0xf000 + dw int1b + dw 0xf000 + dw int1c + dw 0xf000 + dw int1d + dw 0xf000 + dw int1e + +itbl_size dw $-int_table + +; Conversion from CGA video memory colours to ANSI colours + +colour_table db 30, 34, 32, 36, 31, 35, 33, 37 + +; Conversion from non-printable low ASCII to printable + +low_ascii_conv db ' ', 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, '><|!|$', 250, '|^v><--^v' + +; Conversion from UNIX cursor keys/SDL keycodes to scancodes + +unix_cursor_xlt db 0x48, 0x50, 0x4d, 0x4b + +; Conversion from SDL keycodes to Home/End/PgUp/PgDn scancodes + +pgup_pgdn_xlt db 0x47, 0x4f, 0x49, 0x51 + +; Internal variables for VMEM driver + +int8_ctr db 0 +in_update db 0 +vram_dirty db 0 +last_attrib db 0 +int_curpos_x db 0 +int_curpos_y db 0 +crt_curpos_x_last db 0 +crt_curpos_y_last db 0 + +; INT 8 millisecond counter + +last_int8_msec dw 0 +last_key_sdl db 0 + +; Now follow the tables for instruction decode helping + +; R/M mode tables + +rm_mode0_reg1 db 3, 3, 5, 5, 6, 7, 12, 3 +rm_mode012_reg2 db 6, 7, 6, 7, 12, 12, 12, 12 +rm_mode0_disp db 0, 0, 0, 0, 0, 0, 1, 0 +rm_mode0_dfseg db 11, 11, 10, 10, 11, 11, 11, 11 + +rm_mode12_reg1 db 3, 3, 5, 5, 6, 7, 5, 3 +rm_mode12_disp db 1, 1, 1, 1, 1, 1, 1, 1 +rm_mode12_dfseg db 11, 11, 10, 10, 11, 11, 10, 11 + +; Opcode decode tables + +xlat_ids db 9, 9, 9, 9, 7, 7, 25, 26, 9, 9, 9, 9, 7, 7, 25, 48, 9, 9, 9, 9, 7, 7, 25, 26, 9, 9, 9, 9, 7, 7, 25, 26, 9, 9, 9, 9, 7, 7, 27, 28, 9, 9, 9, 9, 7, 7, 27, 28, 9, 9, 9, 9, 7, 7, 27, 29, 9, 9, 9, 9, 7, 7, 27, 29, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 51, 54, 52, 52, 52, 52, 52, 52, 55, 55, 55, 55, 52, 52, 52, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 15, 15, 24, 24, 9, 9, 9, 9, 10, 10, 10, 10, 16, 16, 16, 16, 16, 16, 16, 16, 30, 31, 32, 53, 33, 34, 35, 36, 11, 11, 11, 11, 17, 17, 18, 18, 47, 47, 17, 17, 17, 17, 18, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 19, 19, 37, 37, 20, 20, 49, 50, 19, 19, 38, 39, 40, 19, 12, 12, 12, 12, 41, 42, 43, 44, 53, 53, 53, 53, 53, 53, 53, 53, 13, 13, 13, 13, 21, 21, 22, 22, 14, 14, 14, 14, 21, 21, 22, 22, 53, 0, 23, 23, 53, 45, 6, 6, 46, 46, 46, 46, 46, 46, 5, 5 +ex_data db 0, 0, 0, 0, 0, 0, 8, 8, 1, 1, 1, 1, 1, 1, 9, 36, 2, 2, 2, 2, 2, 2, 10, 10, 3, 3, 3, 3, 3, 3, 11, 11, 4, 4, 4, 4, 4, 4, 8, 0, 5, 5, 5, 5, 5, 5, 9, 1, 6, 6, 6, 6, 6, 6, 10, 2, 7, 7, 7, 7, 7, 7, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 21, 21, 21, 21, 0, 0, 0, 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 16, 22, 0, 0, 0, 0, 1, 1, 0, 255, 48, 2, 0, 0, 0, 0, 255, 255, 40, 11, 3, 3, 3, 3, 3, 3, 3, 3, 43, 43, 43, 43, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 21, 0, 0, 2, 40, 21, 21, 80, 81, 92, 93, 94, 95, 0, 0 +std_flags db 3, 3, 3, 3, 3, 3, 0, 0, 5, 5, 5, 5, 5, 5, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 5, 5, 5, 5, 5, 5, 0, 1, 3, 3, 3, 3, 3, 3, 0, 1, 5, 5, 5, 5, 5, 5, 0, 1, 3, 3, 3, 3, 3, 3, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 5, 5, 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, 0, 0, 5, 5, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +base_size db 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 0, 0, 2, 2, 2, 2, 4, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2 +i_w_adder db 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 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, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +i_mod_adder db 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 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, 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, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1 + +flags_mult db 0, 2, 4, 6, 7, 8, 9, 10, 11 + +jxx_dec_a db 48, 40, 43, 40, 44, 41, 49, 49 +jxx_dec_b db 49, 49, 49, 43, 49, 49, 49, 43 +jxx_dec_c db 49, 49, 49, 49, 49, 49, 44, 44 +jxx_dec_d db 49, 49, 49, 49, 49, 49, 48, 48 + +parity db 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 + +; This is the format of the 36-byte tm structure, returned by the emulator's RTC query call + +timetable: + +tm_sec equ $ +tm_min equ $+4 +tm_hour equ $+8 +tm_mday equ $+12 +tm_mon equ $+16 +tm_year equ $+20 +tm_wday equ $+24 +tm_yday equ $+28 +tm_dst equ $+32 +tm_msec equ $+36 \ No newline at end of file diff --git a/plat/pc86/emu/docs/8086tiny.css b/plat/pc86/emu/docs/8086tiny.css new file mode 100644 index 000000000..2c5018824 --- /dev/null +++ b/plat/pc86/emu/docs/8086tiny.css @@ -0,0 +1,166 @@ +/* +CSS Credit: http://www.templatemo.com/ +*/ +body { + margin:0; + padding:0; + line-height: 1.5em; + font-family: "Trebuchet MS", Verdana, Helvetica, Arial; + font-size: 12px; + color: #000000; + background: #999999 url(images/templatemo_bg.gif); +} +a:link, a:visited { color: #0066CC; text-decoration: none} +a:active, a:hover { color: #008800; text-decoration: underline} + +#templatemo_container_wrapper { + background: url(images/templatemo_side_bg.gif) repeat-x; + padding-left: 2px; +} +#templatemo_container { + width: 809px; + margin: 0px auto; + background: url(images/templatemo_content_bg.gif); +} +#templatemo_top { + clear: left; + height: 25px; /* 'padding-top' + 'height' must be equal to the 'background image height' */ + padding-top: 18px; + padding-left: 30px; + color: #0000bf; + background: url(images/templatemo_top_bg.gif) no-repeat bottom; +} +#templatemo_header { + clear: left; + height: 113px; + text-align: center; + background: url(images/templatemo_header_bg.gif) no-repeat; +} +#inner_header { + height: 88px; + background: url(images/templatemo_header.jpg) no-repeat center center; +} +#templatemo_left_column { + clear: left; + float: left; + width: 540px; + padding-left: 20px; +} +#templatemo_right_column { + float: right; + width: 216px; + padding-right: 15px; +} +#templatemo_footer { + clear: both; + padding-top: 18px; + height: 37px; + text-align: center; + font-size: 11px; + background: url(images/templatemo_footer_bg.gif) no-repeat; + color: #666666; +} +#templatemo_footer a { + color: #666666; +} +#templatemo_site_title { + padding-top: 65px; + font-weight: bold; + font-size: 32px; + color: #FFFFFF; +} +.site_slogan_center { + float:left; + display:block; + width:50px; + height:25px; + text-indent:-999px; + cursor:default; +} +#templatemo_site_slogan { + padding-top: 14px; + font-weight: bold; + font-size: 13px; + color: #AAFFFF; +} +.templatemo_spacer { + clear: left; + height: 18px; +} +.templatemo_pic { + float: left; + margin-right: 10px; + margin-bottom: 10px; + border: 1px solid #000000; +} +.section_box { + margin: 10px; + padding: 10px; + border: 1px dashed #CCCCCC; + background: #F2F2F2; +} +.section_box2 { + clear: left; + margin-top: 10px; + background: #F6F6F6; + color: #000000; +} +.text_area { + padding: 10px; +} +.publish_date { + clear: both; + margin-top: 10px; + color: #999999; + font-size: 11px; + font-weight: bold; +} +.title { + padding-bottom: 12px; + font-size: 18px; + font-weight: bold; + color: #0066CC; +} +.subtitle { + padding-bottom: 6px; + font-size: 14px; + font-weight: bold; + color: #666666; +} +.post_title { + padding: 6px; + padding-left: 10px; + background: #DDEEFF; + font-size: 14px; + font-weight: bold; + color: #0066CC; +} +.templatemo_menu { + list-style-type: none; + margin: 10px; + margin-top: 0px; + padding: 0px; + width: 195px; +} +.templatemo_menu li a{ + background: #F4F4F4 url(images/button_default.gif) no-repeat; + font-size: 13px; + font-weight: bold; + color: #0066CC; + display: block; + width: auto; + margin-bottom: 2px; + padding: 5px; + padding-left: 12px; + text-decoration: none; +} +* html .templatemo_menu li a{ + width: 190px; +} +.templatemo_menu li a:visited, .templatemo_menu li a:active{ + color: #0066CC; +} +.templatemo_menu li a:hover{ + background: #EEEEEE url(images/button_active.gif) no-repeat; + color: #FF3333; +} \ No newline at end of file diff --git a/plat/pc86/emu/docs/doc.html b/plat/pc86/emu/docs/doc.html new file mode 100644 index 000000000..de79c1b7a --- /dev/null +++ b/plat/pc86/emu/docs/doc.html @@ -0,0 +1,345 @@ + + + + +8086tiny: a tiny PC emulator/virtual machine - Documentation + + + + + + +
+
+
+
+8086tiny · Documentation +
+
+ +
+ +
+
+ +
Documentation
+ +

8086tiny is a tiny, free, open source, portable Intel PC emulator/VM, powerful enough to run DOS, Windows 3.0, Excel, MS Flight Simulator, AutoCAD, Lotus 1-2-3, and similar applications. 8086tiny emulates a "late 80's era" PC XT-type machine with the following features:

+ +
    +
  • Intel 8086/186 CPU
  • +
  • 1MB RAM
  • +
  • 3.5" floppy disk controller (1.44MB/720KB)
  • +
  • Fixed disk controller (supports a single hard drive up to 528MB)
  • +
  • CGA/Hercules graphics card with 720x348 2-color and 320x200 4-color graphics (64KB video RAM), and CGA 80 x 25 16-color text mode support
  • +
  • Accurate programmable interrupt timer (PIT)
  • +
  • Keyboard controller with 83-key XT-style keyboard
  • +
  • Real-time clock
  • +
  • PC speaker
  • +
+ +

The emulator uses the SDL graphics library for portability, and compiles under a range of platforms (Windows, Mac OS X, Linux, Android, iOS, Raspberry Pi).

+

While 8086tiny as supplied implements only the 8086 instruction set, it can be extended to more complex, modern instruction sets with relative ease.

+ +
+
Build Instructions
+
+

+ The 8086tiny distribution includes a Makefile that will compile unchanged under most UNIX platforms. The 8086tiny source also compiles unchanged under Microsoft Visual Studio C/C++. +

+
    +
  • Running make compiles the full 8086tiny distribution, which includes audio and CGA/Hercules graphics support via SDL.
  • +
  • To compile for slower platforms (e.g. Raspberry Pi), build with make 8086tiny_slowcpu to increase the graphics emulation frame rate.
  • +
  • If your platform lacks SDL and/or you do not need support for graphics-mode applications, you can compile without graphics and audio support by running make no_graphics to produce a smaller binary.
  • +
+
+
+ +
+
Usage
+
+

8086tiny bios-image-file floppy-image-file [@][harddisk-image-file]

+ +

If harddisk-image-file is prefixed with @ then 8086tiny will boot from the hard disk image. Otherwise, 8086tiny will boot from the floppy disk image.

+ +

Under UNIXes, the keyboard must be set to raw mode using stty for the emulator to run. The distribution includes a script called runme which sets the keyboard mode appropriately and runs the emulator with floppy and/or hard disk images as appropriate:

+ + #!/bin/sh
+ clear
+ stty cbreak raw -echo min 0
+ if [ -f hd.img ]
+ then
+   ./8086tiny bios fd.img hd.img
+ else
+   ./8086tiny bios fd.img
+ fi
+ stty cooked echo +
+
+
+ +
+
Building a Hard Disk Image
+
+

+ To create a hard disk image for use with the emulator, start by generating a flat file called, for example, hd.img of the required size (under 528MB), filled with zero bytes, made using mkfile or a similar tool. +

+

+ Preparing the hard disk image for use with the emulator under DOS is done just like a real PC: +

    +
  • Boot the emulator, and use FDISK to partition the hard disk. When it's done FDISK will reboot the emulator.
  • +
  • Use FORMAT C: (or FORMAT C: /S to create a bootable disk) to format the disk image, and you are done.
  • +
+

The resulting disk image is in the right format to be mounted on a real Windows PC using e.g. OSFMount, on a Mac using hdiutil, or on Linux using mount, providing an easy way to copy files and programs to and from the disk image. Or, you can install programs from within the emulator itself using regular floppy disk images (see "Floppy Drive Emulation" below). +

+
+
+ +
+
Keyboard Support
+
+

+ The emulator simulates an XT-style keyboard controlled by an Intel 8042 chip on I/O port 0x60, generating interrupt 9 on each keypress. Because a real 8042 returns scan codes rather than the ASCII characters, for portability, the emulator BIOS does the reverse of a real PC BIOS and converts ASCII characters to scancodes, simulating press/release of the modifier keys (e.g. shift) as necessary to work like a "real" keyboard. The OS (DOS/Windows) then converts them back to ASCII characters and normally this process works seamlessly. +

+

+ The scan code table in the BIOS maps your local keyboard layout onto scan codes matching a US-layout QWERTY keyboard. If you are using an international keyboard layout everything will work fine with no changes, provided "United States 83-key XT keyboard" or similar is selected if your OS (e.g. Windows 3.0) gives the option. +

+ For console (text) applications, there are special key sequences to get Alt+xxx, Fxx and some Ctrl+xxx keys, since these are not returned directly by the standard C I/O functions: +

    +
  • To send an Alt+xxx key combination, press Ctrl+A then the key, so for example to type Alt+F, press Ctrl+A then F.
  • +
  • To send an Fxx key, press Ctrl+F then a number key. For example, to get the F4 key, press Ctrl+F then 4. To get F10, press Ctrl+F then 0.
  • +
  • To send Ctrl+A, press Ctrl+F then Ctrl+A. To send Ctrl+F, press Ctrl+F then Ctrl+F.
  • +
  • To send a Page Down key, press Ctrl+F then O. To send a Page Up key, press Ctrl+F then Q.
  • +
+

For graphics (SDL) applications, all keys will work as per a "real" PC without needing the special sequences above. +

The keyboard is polled every KEYBOARD_TIMER_UPDATE_DELAY instructions. Decreasing this value will increase keyboard responsiveness, at the expense of emulated CPU speed, and vice versa. The default value of 20000 should be suitable for most platforms.

+
+
+ +
+
Floppy Drive Emulation
+
+

+ Emulates a 3.5" high-density floppy drive. Can read, write and format 1.44MB disks (18 sectors per track, 2 heads) and 720KB disks (9 sectors per track, 2 heads). +

+ If you want to install your own software from a set of multiple floppy images (downloaded from e.g. Vetusware), the easiest way to "change disks" is to copy each disk image in turn over the floppy image file you specified on the command line, from a terminal other than the one running 8086tiny. Don't forget to put your original boot disk back at the end! +

+
+
+ +
+
Hard Drive Emulation
+
+

+ Supports up to 1023 cylinders, 63 sectors per track, 63 heads for disks up to 528MB. +

+ Disk image format used is a subset of the standard "raw" format used by most disk image mount tools. In general, disk images prepared by the emulator will work with disk image tools and other emulators, but not the other way around. +

+ The emulator uses an overly simplistic algorithm to derive a simulated cylinder/sector/head geometry from the disk image file's size. This algorithm often results in not all the space in the image file being available for disk partitions. For example, creating a 40,000,000 byte image file results in DOS FDISK seeing only 31.9MB as the volume size. +

+ 8086tiny will boot from a hard disk image if the HD image filename is prefixed with @ on the command line. For example: ./8086tiny bios fd.img @hd.img +

+
+
+ +
+
Text Mode Support
+
+

+ The emulator supports text output via the standard BIOS interrupt 0x10 interface, and also direct video memory access (one page, 4KB video RAM at segment B800) in 80 x 25 CGA 16-color text mode. +

+ BIOS text output calls are converted to simple writes to stdout. Direct video memory accesses for the 80 x 25 CGA color text mode are converted to ANSI terminal escape sequences. If you are using a terminal which does not support ANSI (e.g. you are compiling the emulator with MS VC++ and running in a Windows console window) then PC applications that directly write to video memory in text mode may be unusable. Please make sure your terminal window is at least 80 x 25 characters in size. +

+ Video memory in text mode is rendered to the terminal every 8 * KEYBOARD_TIMER_UPDATE_DELAY instructions. Decreasing this value will increase the text update rate, at the expense of emulated CPU speed, and vice versa. The default value of 20000 should be suitable for most platforms.

+

+ The regular PC character code page (437) includes various extended ASCII characters for things like line drawing. You might want to set the font in your terminal program to something that includes these (for example, on Mac OS X there is a suitable freeware font called Perfect DOS VGA 437). Otherwise, extended characters may render incorrectly (for example as question mark symbols). +

+ Occasionally a DOS application on exit will leave the video hardware in an odd state which confuses the emulator, resulting in subsequent text output being invisible. If this happens, just use the DOS CLS command to clear the screen and all will be well again. +

+
+
+ +
+
Graphics Mode Support
+
+

+ Hercules 720x348 monochrome graphics mode and CGA 320x200 4-color graphics mode are emulated using SDL. Most CGA/Hercules features are supported via the normal I/O interface on ports 0x3Dx and 0x3Bx including video memory bank switching (segments B000/B800), which some games use for double-buffered graphics. Resolution reprogramming via the CRTC register is supported by 8086tiny 1.03 and later, as required by, for example, the ETEN Chinese System (which uses 640 x 408). The CGA 640x200 2-color mode is currently not supported. +

+ When an application enters graphics mode, the emulator will open an SDL window (which will be closed when the application goes back to text mode). On UNIXes, SDL will automatically output graphics via X11 if the DISPLAY environment variable is set up. +

+ The graphics display is updated every GRAPHICS_UPDATE_DELAY instructions. Decreasing this value will increase the graphics update rate, at the expense of emulated CPU speed, and vice versa. By default, GRAPHICS_UPDATE_DELAY is set to 360000 instructions, which gives good performance for faster platforms. On slower platforms like Raspberry Pi, a smaller value is suitable: building with make 8086tiny_slowcpu reduces GRAPHICS_UPDATE_DELAY to 50000 instructions. +

+ Some applications (e.g. AutoCAD) support a PC configuration with a CGA card and a Hercules card, for simultaneous text and graphics output on different displays. The emulator simulates this configuration, too, using separate windows for the (terminal) text and (SDL) graphics displays. +

+ If your application only requires text mode, you can compile 8086tiny without SDL by defining NO_GRAPHICS. +

+
+
+ +
+
Real-Time Clock Support
+
+

+ Reading the RTC (both time and date) is emulated via the standard BIOS clock interface, pulling the time/date from the host computer. Setting the time or date is not supported. +

+
+
+ +
+
Hardware Timer Support
+
+

+ Programmable interrupt timer channels 0 and 2 are emulated through the usual I/O port 0x40-0x43 interface. Only mode 3 (square wave generator) is currently supported, but this is what most applications use. Software that uses timers to control execution speed such as games should run at an accurate pace. +

+
+
+ +
+
PC Speaker Support
+
+

+ The PC speaker is emulated through the usual port 0x61 interface. The only PC speaker mode supported is via PIT channel 2, so you will hear most music but not non-musical sound effects. +

+
+
+ +
+
BIOS
+
+

+ Like a real PC, the emulator needs a BIOS to implement boot functionality and the standard interrupt interfaces. The 8086tiny BIOS was written from scratch using documentation in the public domain. It is around 6KB in size and assembles using NASM. Full source code and a pre-built binary are provided. +

+ The BIOS binary comprises a code section and a data section. The code section implements the standard interrupt interfaces for video, disk, timer, clock and so on, much as a "real" PC BIOS does, and also a small timer-controlled video driver to convert video memory formatting into ANSI escape sequences when the emulator is in text mode. + The data section includes typical BIOS structures like a scan code table and the BIOS data area, but also a number of look-up tables to assist the emulator with instruction decoding. Full detail is provided in the "CPU, Memory and Register Emulation" section below. +

+
+
+ +
+
Memory Map and Register Emulation
+
+

+ The emulator simulates a hardware configuration with A20 address line wraparound disabled, making just over 1MB of RAM available to applications. +

+ Memory map is largely as per a real PC, with interrupt vector table at 0:0, BIOS data area including keyboard buffer at 40:0, CGA text video memory at B800:0, Hercules/CGA graphics memory at B000/B800:0, and BIOS at F000:0100. Unlike a real PC, in the emulator the CPU registers are memory-mapped (at F000:0), which enables considerable optimisation of the emulator's instruction execution unit by permitting the unification of memory and register operations, while remaining invisible to the running software. +

+
+
+ +
+
CPU, Memory and Register Emulation
+
+

+ CPU supports the full 8086 instruction set (plus some 80186 extensions), including undocumented instructions (e.g. SALC) and flag behaviors (e.g. MUL/DIV), opcode bugs which some applications rely on (e.g. PUSH SP), and the trap flag for debugger support. +

+ The focus of 8086tiny is on minimizing code size without comproming emulation accuracy. Due to the complexities of the highly irregular Intel x86 instruction format, instruction decoding is assisted by a number of lookup tables which form part of the BIOS binary. For example, there are many different ways to encode a MOV instruction, depending on the types of the source and destination operands (immediate, register, or memory). There are sometimes even multiple ways to encode the same instruction (e.g. MOV AX, [1234] usually encodes as A1 34 12 but can also encode as 8B 06 34 12). To avoid having to implement similar functionality in the emulator multiple times for each instruction or encoding variant, look-up tables are used to map each instruction to an internal function and subfunction number. +

+ As an example, we illustrate how the emulator executes the instruction ADD AX, BX, which encodes as hex 01 D8. +

+
    +
  • The emulator begins by retrieving index 01 hex (the first byte of the instruction) from TABLE_XLAT_OPCODE and TABLE_XLAT_SUBFUNCTION, giving a translated opcode ID of decimal 9 (which corresponds to the Intel instruction template arithmetic_function reg, r/m) and a subfunction ID of 0 (which corresponds to the ADD function), respectively.
  • +
  • The OPCODE chain in the source uses the translated opcode ID and subfunction ID to determine the operation to execute, in this case calling the OP(+=) macro followed by set_CF() to set the carry flag in accordance with the result of the addition.
  • +
  • Next, instruction length is computed. Because Intel x86 instructions are of arbitrary length (and, sometimes, multiple encodings of the same instruction can have different lengths), tables are used to determine the instruction length to move IP to the next instruction. The opcode index 01 hex is used as an index into TABLE_BASE_INST_SIZE, TABLE_I_MOD_SIZE, and TABLE_I_W_SIZE and these numbers are added to compute the total instruction length.
  • +
  • Finally, flags are set. The opcode index 01 hex is then used as an index into TABLE_STD_FLAGS to give a bitmask of 3, which is FLAGS_UPDATE_SZP | FLAGS_UPDATE_AO_ARITH.
  • +
    • FLAGS_UPDATE_SZP (1) signifies that this instruction sets the sign, zero and parity flags according to the operation's result in the standard way. Sign and zero flags are set directly from the result, and the parity flag is set by looking up the result in TABLE_PARITY_FLAG.
    • +
    • FLAGS_UPDATE_AO_ARITH (2) signifies that this instruction sets the auxiliary and overflow flags as standard for arithmetic operations.
    • +
    • If FLAGS_UPDATE_OC_LOGIC (4) were set in the bitmask (it is not here), the overflow and carry flags would be set to 0, as standard for logic operations.
    +
+ +

The CPU also implements some "special" two-byte opcodes to help the emulator talk with the outside world. These are: +

    +
  • 0F 00 (PUTCHAR_AL) - output character in register AL to terminal
  • +
  • 0F 01 (GET_RTC) - write real-time clock data (as returned by localtime) to memory location ES:BX
  • +
  • 0F 02 (READ_DISK) - read AX bytes from disk at offset 512*(16*SI+BP) into memory location ES:BX. Disk is specified in DL (0 = hard disk, 1 = floppy disk)
  • +
  • 0F 03 (WRITE_DISK) - write AX bytes at memory location ES:BX to disk at offset 512*(16*SI+BP). Disk is specified in DL as per 0F 02
  • +
+ +

Emulator exit is triggered by a JMP 0:0 instruction, to allow the user to easily quit the emulator without shutting down the terminal.

+

Extension of the instruction set supported by 8086tiny can be implemented by appropriate modification to the tables described above in the BIOS source, and addition of a corresponding new OPCODE block in the C source.

+

+
+
+ +
+
Supported Application Software
+
+

+ The emulator will run practically any software a real PC (of the spec listed at the top of this page) can. +

+ The author has successfully tested a range of software on the emulator. +

    +
  • OSes/GUIs
  • +
    • MS-DOS 6.22
    • +
    • FreeDOS 0.82pl3
    • +
    • Linux/ELKS 0.1.4
    • +
    • Windows 3.0
    • +
    • DESQview 2.8
    • +
    • ETEN Chinese System
    +
  • Professional software
  • +
    • Lotus 1-2-3 R2.4
    • +
    • AsEasyAs 5.7
    • +
    • Excel 2.1 for Windows
    • +
    • AutoCAD 2.5
    • +
    • WordStar 4
    +
  • Programming languages
  • +
    • QBASIC
    • +
    • GWBASIC
    • +
    • Turbo C++
    +
  • Games
  • +
    • Carrier Command
    • +
    • Police Quest
    • +
    • SimCity
    • +
    • Alley Cat
    • +
    • MS Flight Simulator 4
    • +
    • Lots of freeware Windows games
    +
  • Diagnostic/benchmark software
  • +
    • Manifest
    • +
    • Microsoft MSD
    • +
    • InfoSpot
    • +
    • CheckIt
    +
+

+
+
+ +
+
+ +
+ + + +
+
Author Contact
+ Adrian Cable
+ adrian.cable@gmail.com
+ +

If 8086tiny brings you joy or profit, the author welcomes modest donations as a token of appreciation.

+ +
+ + + + +
+ +
+
+ + + +
+
+
+ + + diff --git a/plat/pc86/emu/docs/images/button_active.gif b/plat/pc86/emu/docs/images/button_active.gif new file mode 100644 index 000000000..cad8e601f Binary files /dev/null and b/plat/pc86/emu/docs/images/button_active.gif differ diff --git a/plat/pc86/emu/docs/images/button_default.gif b/plat/pc86/emu/docs/images/button_default.gif new file mode 100644 index 000000000..35a0835e7 Binary files /dev/null and b/plat/pc86/emu/docs/images/button_default.gif differ diff --git a/plat/pc86/emu/docs/images/logo.gif b/plat/pc86/emu/docs/images/logo.gif new file mode 100644 index 000000000..a32bab7ca Binary files /dev/null and b/plat/pc86/emu/docs/images/logo.gif differ diff --git a/plat/pc86/emu/docs/images/templatemo_bg.gif b/plat/pc86/emu/docs/images/templatemo_bg.gif new file mode 100644 index 000000000..f72e5d015 Binary files /dev/null and b/plat/pc86/emu/docs/images/templatemo_bg.gif differ diff --git a/plat/pc86/emu/docs/images/templatemo_content_bg.gif b/plat/pc86/emu/docs/images/templatemo_content_bg.gif new file mode 100644 index 000000000..0727be22e Binary files /dev/null and b/plat/pc86/emu/docs/images/templatemo_content_bg.gif differ diff --git a/plat/pc86/emu/docs/images/templatemo_footer_bg.gif b/plat/pc86/emu/docs/images/templatemo_footer_bg.gif new file mode 100644 index 000000000..7d11c1a07 Binary files /dev/null and b/plat/pc86/emu/docs/images/templatemo_footer_bg.gif differ diff --git a/plat/pc86/emu/docs/images/templatemo_header_bg.gif b/plat/pc86/emu/docs/images/templatemo_header_bg.gif new file mode 100644 index 000000000..b9f875449 Binary files /dev/null and b/plat/pc86/emu/docs/images/templatemo_header_bg.gif differ diff --git a/plat/pc86/emu/docs/images/templatemo_side_bg.gif b/plat/pc86/emu/docs/images/templatemo_side_bg.gif new file mode 100644 index 000000000..bc8512370 Binary files /dev/null and b/plat/pc86/emu/docs/images/templatemo_side_bg.gif differ diff --git a/plat/pc86/emu/docs/images/templatemo_top_bg.gif b/plat/pc86/emu/docs/images/templatemo_top_bg.gif new file mode 100644 index 000000000..f18292512 Binary files /dev/null and b/plat/pc86/emu/docs/images/templatemo_top_bg.gif differ diff --git a/plat/pc86/emu/license.txt b/plat/pc86/emu/license.txt new file mode 100644 index 000000000..b99d26cc0 --- /dev/null +++ b/plat/pc86/emu/license.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2014 Adrian Cable - http://www.megalith.co.uk/8086tiny + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/plat/pc86/emu/runme b/plat/pc86/emu/runme new file mode 100755 index 000000000..450aa08eb --- /dev/null +++ b/plat/pc86/emu/runme @@ -0,0 +1,10 @@ +#!/bin/sh +clear +stty cbreak raw -echo min 0 +if [ -f hd.img ] +then + ./8086tiny bios fd.img hd.img +else + ./8086tiny bios fd.img +fi +stty cooked echo