├── .gitignore ├── 8086tiny.c ├── Makefile ├── README.md ├── bios ├── bios_source └── bios.asm ├── docs ├── 8086tiny.css ├── doc.html └── images │ ├── QNX2_8086tiny.png │ ├── button_active.gif │ ├── button_default.gif │ ├── logo.gif │ ├── templatemo_bg.gif │ ├── templatemo_content_bg.gif │ ├── templatemo_footer_bg.gif │ ├── templatemo_header_bg.gif │ ├── templatemo_side_bg.gif │ └── templatemo_top_bg.gif ├── fd.img ├── license.txt └── runme /.gitignore: -------------------------------------------------------------------------------- 1 | 8086tiny 2 | bios_source/a.out 3 | bios_source/bios 4 | -------------------------------------------------------------------------------- /8086tiny.c: -------------------------------------------------------------------------------- 1 | // 8086tiny: a tiny, highly functional, highly portable PC emulator/VM 2 | // Copyright 2013-14, Adrian Cable (adrian.cable@gmail.com) - http://www.megalith.co.uk/8086tiny 3 | // 4 | // Revision 1.25 5 | // 6 | // This work is licensed under the MIT License. See included LICENSE.TXT. 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #ifndef _WIN32 13 | #include 14 | #include 15 | #endif 16 | 17 | #ifndef NO_GRAPHICS 18 | #include "SDL.h" 19 | #endif 20 | 21 | // Emulator system constants 22 | #define IO_PORT_COUNT 0x10000 23 | #define RAM_SIZE 0x10FFF0 24 | #define REGS_BASE 0xF0000 25 | #define VIDEO_RAM_SIZE 0x10000 26 | 27 | // Graphics/timer/keyboard update delays (explained later) 28 | #ifndef GRAPHICS_UPDATE_DELAY 29 | #define GRAPHICS_UPDATE_DELAY 360000 30 | #endif 31 | #define KEYBOARD_TIMER_UPDATE_DELAY 20000 32 | 33 | // 16-bit register decodes 34 | #define REG_AX 0 35 | #define REG_CX 1 36 | #define REG_DX 2 37 | #define REG_BX 3 38 | #define REG_SP 4 39 | #define REG_BP 5 40 | #define REG_SI 6 41 | #define REG_DI 7 42 | 43 | #define REG_ES 8 44 | #define REG_CS 9 45 | #define REG_SS 10 46 | #define REG_DS 11 47 | 48 | #define REG_ZERO 12 49 | #define REG_SCRATCH 13 50 | 51 | // 8-bit register decodes 52 | #define REG_AL 0 53 | #define REG_AH 1 54 | #define REG_CL 2 55 | #define REG_CH 3 56 | #define REG_DL 4 57 | #define REG_DH 5 58 | #define REG_BL 6 59 | #define REG_BH 7 60 | 61 | // FLAGS register decodes 62 | #define FLAG_CF 40 63 | #define FLAG_PF 41 64 | #define FLAG_AF 42 65 | #define FLAG_ZF 43 66 | #define FLAG_SF 44 67 | #define FLAG_TF 45 68 | #define FLAG_IF 46 69 | #define FLAG_DF 47 70 | #define FLAG_OF 48 71 | 72 | // Lookup tables in the BIOS binary 73 | #define TABLE_XLAT_OPCODE 8 74 | #define TABLE_XLAT_SUBFUNCTION 9 75 | #define TABLE_STD_FLAGS 10 76 | #define TABLE_PARITY_FLAG 11 77 | #define TABLE_BASE_INST_SIZE 12 78 | #define TABLE_I_W_SIZE 13 79 | #define TABLE_I_MOD_SIZE 14 80 | #define TABLE_COND_JUMP_DECODE_A 15 81 | #define TABLE_COND_JUMP_DECODE_B 16 82 | #define TABLE_COND_JUMP_DECODE_C 17 83 | #define TABLE_COND_JUMP_DECODE_D 18 84 | #define TABLE_FLAGS_BITFIELDS 19 85 | 86 | // Bitfields for TABLE_STD_FLAGS values 87 | #define FLAGS_UPDATE_SZP 1 88 | #define FLAGS_UPDATE_AO_ARITH 2 89 | #define FLAGS_UPDATE_OC_LOGIC 4 90 | 91 | // Helper macros 92 | 93 | // Decode mod, r_m and reg fields in instruction 94 | #define DECODE_RM_REG scratch2_uint = 4 * !i_mod, \ 95 | 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), \ 96 | op_from_addr = GET_REG_ADDR(i_reg), \ 97 | i_d && (scratch_uint = op_from_addr, op_from_addr = rm_addr, op_to_addr = scratch_uint) 98 | 99 | // Return memory-mapped register location (offset into mem array) for register #reg_id 100 | #define GET_REG_ADDR(reg_id) (REGS_BASE + (i_w ? 2 * reg_id : 2 * reg_id + reg_id / 4 & 7)) 101 | 102 | // Returns number of top bit in operand (i.e. 8 for 8-bit operands, 16 for 16-bit operands) 103 | #define TOP_BIT 8*(i_w + 1) 104 | 105 | // Opcode execution unit helpers 106 | #define OPCODE ;break; case 107 | #define OPCODE_CHAIN ; case 108 | 109 | // [I]MUL/[I]DIV/DAA/DAS/ADC/SBB helpers 110 | #define MUL_MACRO(op_data_type,out_regs) (set_opcode(0x10), \ 111 | out_regs[i_w + 1] = (op_result = CAST(op_data_type)mem[rm_addr] * (op_data_type)*out_regs) >> 16, \ 112 | regs16[REG_AX] = op_result, \ 113 | set_OF(set_CF(op_result - (op_data_type)op_result))) 114 | #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) 115 | #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))), \ 116 | set_CF((((mask & 1 ? scratch2_uint : regs8[REG_AL]) & mask) > min) || regs8[FLAG_CF]) && (op_result = regs8[REG_AL] op1 0x60) 117 | #define ADC_SBB_MACRO(a) OP(a##= regs8[FLAG_CF] +), \ 118 | set_CF(regs8[FLAG_CF] && (op_result == op_dest) || (a op_result < a(int)op_dest)), \ 119 | set_AF_OF_arith() 120 | 121 | // Execute arithmetic/logic operations in emulator memory/registers 122 | #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) \ 123 | : (op_dest = dest, op_result = dest op (op_source = CAST(unsigned char)src))) 124 | #define MEM_OP(dest,op,src) R_M_OP(mem[dest],op,mem[src]) 125 | #define OP(op) MEM_OP(op_to_addr,op,op_from_addr) 126 | 127 | // Increment or decrement a register #reg_id (usually SI or DI), depending on direction flag and operand size (given by i_w) 128 | #define INDEX_INC(reg_id) (regs16[reg_id] -= (2 * regs8[FLAG_DF] - 1)*(i_w + 1)) 129 | 130 | // Helpers for stack operations 131 | #define R_M_PUSH(a) (i_w = 1, R_M_OP(mem[SEGREG(REG_SS, REG_SP, --)], =, a)) 132 | #define R_M_POP(a) (i_w = 1, regs16[REG_SP] += 2, R_M_OP(a, =, mem[SEGREG(REG_SS, REG_SP, -2+)])) 133 | 134 | // Convert segment:offset to linear address in emulator memory space 135 | #define SEGREG(reg_seg,reg_ofs,op) 16 * regs16[reg_seg] + (unsigned short)(op regs16[reg_ofs]) 136 | 137 | // Returns sign bit of an 8-bit or 16-bit operand 138 | #define SIGN_OF(a) (1 & (i_w ? CAST(short)a : a) >> (TOP_BIT - 1)) 139 | 140 | // Reinterpretation cast 141 | #define CAST(a) *(a*)& 142 | 143 | // Keyboard driver for console. This may need changing for UNIX/non-UNIX platforms 144 | #ifdef _WIN32 145 | #define KEYBOARD_DRIVER kbhit() && (mem[0x4A6] = getch(), pc_interrupt(7)) 146 | #else 147 | #define KEYBOARD_DRIVER read(0, mem + 0x4A6, 1) && (int8_asap = (mem[0x4A6] == 0x1B), pc_interrupt(7)) 148 | #endif 149 | 150 | // Keyboard driver for SDL 151 | #ifdef NO_GRAPHICS 152 | #define SDL_KEYBOARD_DRIVER KEYBOARD_DRIVER 153 | #else 154 | #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) 155 | #endif 156 | 157 | // Global variable definitions 158 | 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]; 159 | unsigned short *regs16, reg_ip, seg_override, file_index, wave_counter; 160 | 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; 161 | int op_result, disk[3], scratch_int; 162 | time_t clock_buf; 163 | struct timeb ms_clock; 164 | 165 | #ifndef NO_GRAPHICS 166 | SDL_AudioSpec sdl_audio = {44100, AUDIO_U8, 1, 0, 128}; 167 | SDL_Surface *sdl_screen; 168 | SDL_Event sdl_event; 169 | unsigned short vid_addr_lookup[VIDEO_RAM_SIZE], cga_colors[4] = {0 /* Black */, 0x1F1F /* Cyan */, 0xE3E3 /* Magenta */, 0xFFFF /* White */}; 170 | #endif 171 | 172 | // Helper functions 173 | 174 | // Set carry flag 175 | char set_CF(int new_CF) 176 | { 177 | return regs8[FLAG_CF] = !!new_CF; 178 | } 179 | 180 | // Set auxiliary flag 181 | char set_AF(int new_AF) 182 | { 183 | return regs8[FLAG_AF] = !!new_AF; 184 | } 185 | 186 | // Set overflow flag 187 | char set_OF(int new_OF) 188 | { 189 | return regs8[FLAG_OF] = !!new_OF; 190 | } 191 | 192 | // Set auxiliary and overflow flag after arithmetic operations 193 | char set_AF_OF_arith() 194 | { 195 | set_AF((op_source ^= op_dest ^ op_result) & 0x10); 196 | if (op_result == op_dest) 197 | return set_OF(0); 198 | else 199 | return set_OF(1 & (regs8[FLAG_CF] ^ op_source >> (TOP_BIT - 1))); 200 | } 201 | 202 | // Assemble and return emulated CPU FLAGS register in scratch_uint 203 | void make_flags() 204 | { 205 | scratch_uint = 0xF002; // 8086 has reserved and unused flags set to 1 206 | for (int i = 9; i--;) 207 | scratch_uint += regs8[FLAG_CF + i] << bios_table_lookup[TABLE_FLAGS_BITFIELDS][i]; 208 | } 209 | 210 | // Set emulated CPU FLAGS register from regs8[FLAG_xx] values 211 | void set_flags(int new_flags) 212 | { 213 | for (int i = 9; i--;) 214 | regs8[FLAG_CF + i] = !!(1 << bios_table_lookup[TABLE_FLAGS_BITFIELDS][i] & new_flags); 215 | } 216 | 217 | // Convert raw opcode to translated opcode index. This condenses a large number of different encodings of similar 218 | // instructions into a much smaller number of distinct functions, which we then execute 219 | void set_opcode(unsigned char opcode) 220 | { 221 | xlat_opcode_id = bios_table_lookup[TABLE_XLAT_OPCODE][raw_opcode_id = opcode]; 222 | extra = bios_table_lookup[TABLE_XLAT_SUBFUNCTION][opcode]; 223 | i_mod_size = bios_table_lookup[TABLE_I_MOD_SIZE][opcode]; 224 | set_flags_type = bios_table_lookup[TABLE_STD_FLAGS][opcode]; 225 | } 226 | 227 | // Execute INT #interrupt_num on the emulated machine 228 | char pc_interrupt(unsigned char interrupt_num) 229 | { 230 | set_opcode(0xCD); // Decode like INT 231 | 232 | make_flags(); 233 | R_M_PUSH(scratch_uint); 234 | R_M_PUSH(regs16[REG_CS]); 235 | R_M_PUSH(reg_ip); 236 | MEM_OP(REGS_BASE + 2 * REG_CS, =, 4 * interrupt_num + 2); 237 | R_M_OP(reg_ip, =, mem[4 * interrupt_num]); 238 | 239 | return regs8[FLAG_TF] = regs8[FLAG_IF] = 0; 240 | } 241 | 242 | // AAA and AAS instructions - which_operation is +1 for AAA, and -1 for AAS 243 | int AAA_AAS(char which_operation) 244 | { 245 | return (regs16[REG_AX] += 262 * which_operation*set_AF(set_CF(((regs8[REG_AL] & 0x0F) > 9) || regs8[FLAG_AF])), regs8[REG_AL] &= 0x0F); 246 | } 247 | 248 | #ifndef NO_GRAPHICS 249 | void audio_callback(void *data, unsigned char *stream, int len) 250 | { 251 | for (int i = 0; i < len; i++) 252 | stream[i] = (spkr_en == 3) && CAST(unsigned short)mem[0x4AA] ? -((54 * wave_counter++ / CAST(unsigned short)mem[0x4AA]) & 1) : sdl_audio.silence; 253 | 254 | spkr_en = io_ports[0x61] & 3; 255 | } 256 | #endif 257 | 258 | // Emulator entry point 259 | int main(int argc, char **argv) 260 | { 261 | #ifndef NO_GRAPHICS 262 | // Initialise SDL 263 | SDL_Init(SDL_INIT_AUDIO); 264 | sdl_audio.callback = audio_callback; 265 | #ifdef _WIN32 266 | sdl_audio.samples = 512; 267 | #endif 268 | SDL_OpenAudio(&sdl_audio, 0); 269 | #endif 270 | 271 | // regs16 and reg8 point to F000:0, the start of memory-mapped registers. CS is initialised to F000 272 | regs16 = (unsigned short *)(regs8 = mem + REGS_BASE); 273 | regs16[REG_CS] = 0xF000; 274 | 275 | // Trap flag off 276 | regs8[FLAG_TF] = 0; 277 | 278 | // Set DL equal to the boot device: 0 for the FD, or 0x80 for the HD. Normally, boot from the FD. 279 | // But, if the HD image file is prefixed with @, then boot from the HD 280 | regs8[REG_DL] = ((argc > 3) && (*argv[3] == '@')) ? argv[3]++, 0x80 : 0; 281 | 282 | // Open BIOS (file id disk[2]), floppy disk image (disk[1]), and hard disk image (disk[0]) if specified 283 | for (file_index = 3; file_index;) 284 | disk[--file_index] = *++argv ? open(*argv, 32898) : 0; 285 | 286 | // Set CX:AX equal to the hard disk image size, if present 287 | CAST(unsigned)regs16[REG_AX] = *disk ? lseek(*disk, 0, 2) >> 9 : 0; 288 | 289 | // Load BIOS image into F000:0100, and set IP to 0100 290 | read(disk[2], regs8 + (reg_ip = 0x100), 0xFF00); 291 | 292 | // Load instruction decoding helper table 293 | for (int i = 0; i < 20; i++) 294 | for (int j = 0; j < 256; j++) 295 | bios_table_lookup[i][j] = regs8[regs16[0x81 + i] + j]; 296 | 297 | // Instruction execution loop. Terminates if CS:IP = 0:0 298 | for (; opcode_stream = mem + 16 * regs16[REG_CS] + reg_ip, opcode_stream != mem;) 299 | { 300 | // Set up variables to prepare for decoding an opcode 301 | set_opcode(*opcode_stream); 302 | 303 | // Extract i_w and i_d fields from instruction 304 | i_w = (i_reg4bit = raw_opcode_id & 7) & 1; 305 | i_d = i_reg4bit / 2 & 1; 306 | 307 | // Extract instruction data fields 308 | i_data0 = CAST(short)opcode_stream[1]; 309 | i_data1 = CAST(short)opcode_stream[2]; 310 | i_data2 = CAST(short)opcode_stream[3]; 311 | 312 | // seg_override_en and rep_override_en contain number of instructions to hold segment override and REP prefix respectively 313 | if (seg_override_en) 314 | seg_override_en--; 315 | if (rep_override_en) 316 | rep_override_en--; 317 | 318 | // i_mod_size > 0 indicates that opcode uses i_mod/i_rm/i_reg, so decode them 319 | if (i_mod_size) 320 | { 321 | i_mod = (i_data0 & 0xFF) >> 6; 322 | i_rm = i_data0 & 7; 323 | i_reg = i_data0 / 8 & 7; 324 | 325 | if ((!i_mod && i_rm == 6) || (i_mod == 2)) 326 | i_data2 = CAST(short)opcode_stream[4]; 327 | else if (i_mod != 1) 328 | i_data2 = i_data1; 329 | else // If i_mod is 1, operand is (usually) 8 bits rather than 16 bits 330 | i_data1 = (char)i_data1; 331 | 332 | DECODE_RM_REG; 333 | } 334 | 335 | // Instruction execution unit 336 | switch (xlat_opcode_id) 337 | { 338 | OPCODE_CHAIN 0: // Conditional jump (JAE, JNAE, etc.) 339 | // i_w is the invert flag, e.g. i_w == 1 means JNAE, whereas i_w == 0 means JAE 340 | scratch_uchar = raw_opcode_id / 2 & 7; 341 | 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]])) 342 | OPCODE 1: // MOV reg, imm 343 | i_w = !!(raw_opcode_id & 8); 344 | R_M_OP(mem[GET_REG_ADDR(i_reg4bit)], =, i_data0) 345 | OPCODE 3: // PUSH regs16 346 | R_M_PUSH(regs16[i_reg4bit]) 347 | OPCODE 4: // POP regs16 348 | R_M_POP(regs16[i_reg4bit]) 349 | OPCODE 2: // INC|DEC regs16 350 | i_w = 1; 351 | i_d = 0; 352 | i_reg = i_reg4bit; 353 | DECODE_RM_REG; 354 | i_reg = extra 355 | OPCODE_CHAIN 5: // INC|DEC|JMP|CALL|PUSH 356 | if (i_reg < 2) // INC|DEC 357 | MEM_OP(op_from_addr, += 1 - 2 * i_reg +, REGS_BASE + 2 * REG_ZERO), 358 | op_source = 1, 359 | set_AF_OF_arith(), 360 | set_OF(op_dest + 1 - i_reg == 1 << (TOP_BIT - 1)), 361 | (xlat_opcode_id == 5) && (set_opcode(0x10), 0); // Decode like ADC 362 | else if (i_reg != 6) // JMP|CALL 363 | i_reg - 3 || R_M_PUSH(regs16[REG_CS]), // CALL (far) 364 | i_reg & 2 && R_M_PUSH(reg_ip + 2 + i_mod*(i_mod != 3) + 2*(!i_mod && i_rm == 6)), // CALL (near or far) 365 | i_reg & 1 && (regs16[REG_CS] = CAST(short)mem[op_from_addr + 2]), // JMP|CALL (far) 366 | R_M_OP(reg_ip, =, mem[op_from_addr]), 367 | set_opcode(0x9A); // Decode like CALL 368 | else // PUSH 369 | R_M_PUSH(mem[rm_addr]) 370 | OPCODE 6: // TEST r/m, imm16 / NOT|NEG|MUL|IMUL|DIV|IDIV reg 371 | op_to_addr = op_from_addr; 372 | 373 | switch (i_reg) 374 | { 375 | OPCODE_CHAIN 0: // TEST 376 | set_opcode(0x20); // Decode like AND 377 | reg_ip += i_w + 1; 378 | R_M_OP(mem[op_to_addr], &, i_data2) 379 | OPCODE 2: // NOT 380 | OP(=~) 381 | OPCODE 3: // NEG 382 | OP(=-); 383 | op_dest = 0; 384 | set_opcode(0x28); // Decode like SUB 385 | set_CF(op_result > op_dest) 386 | OPCODE 4: // MUL 387 | i_w ? MUL_MACRO(unsigned short, regs16) : MUL_MACRO(unsigned char, regs8) 388 | OPCODE 5: // IMUL 389 | i_w ? MUL_MACRO(short, regs16) : MUL_MACRO(char, regs8) 390 | OPCODE 6: // DIV 391 | i_w ? DIV_MACRO(unsigned short, unsigned, regs16) : DIV_MACRO(unsigned char, unsigned short, regs8) 392 | OPCODE 7: // IDIV 393 | i_w ? DIV_MACRO(short, int, regs16) : DIV_MACRO(char, short, regs8); 394 | } 395 | OPCODE 7: // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP AL/AX, immed 396 | rm_addr = REGS_BASE; 397 | i_data2 = i_data0; 398 | i_mod = 3; 399 | i_reg = extra; 400 | reg_ip--; 401 | OPCODE_CHAIN 8: // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP reg, immed 402 | op_to_addr = rm_addr; 403 | regs16[REG_SCRATCH] = (i_d |= !i_w) ? (char)i_data2 : i_data2; 404 | op_from_addr = REGS_BASE + 2 * REG_SCRATCH; 405 | reg_ip += !i_d + 1; 406 | set_opcode(0x08 * (extra = i_reg)); 407 | OPCODE_CHAIN 9: // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP|MOV reg, r/m 408 | switch (extra) 409 | { 410 | OPCODE_CHAIN 0: // ADD 411 | OP(+=), 412 | set_CF(op_result < op_dest) 413 | OPCODE 1: // OR 414 | OP(|=) 415 | OPCODE 2: // ADC 416 | ADC_SBB_MACRO(+) 417 | OPCODE 3: // SBB 418 | ADC_SBB_MACRO(-) 419 | OPCODE 4: // AND 420 | OP(&=) 421 | OPCODE 5: // SUB 422 | OP(-=), 423 | set_CF(op_result > op_dest) 424 | OPCODE 6: // XOR 425 | OP(^=) 426 | OPCODE 7: // CMP 427 | OP(-), 428 | set_CF(op_result > op_dest) 429 | OPCODE 8: // MOV 430 | OP(=); 431 | } 432 | OPCODE 10: // MOV sreg, r/m | POP r/m | LEA reg, r/m 433 | if (!i_w) // MOV 434 | i_w = 1, 435 | i_reg += 8, 436 | DECODE_RM_REG, 437 | OP(=); 438 | else if (!i_d) // LEA 439 | seg_override_en = 1, 440 | seg_override = REG_ZERO, 441 | DECODE_RM_REG, 442 | R_M_OP(mem[op_from_addr], =, rm_addr); 443 | else // POP 444 | R_M_POP(mem[rm_addr]) 445 | OPCODE 11: // MOV AL/AX, [loc] 446 | i_mod = i_reg = 0; 447 | i_rm = 6; 448 | i_data1 = i_data0; 449 | DECODE_RM_REG; 450 | MEM_OP(op_from_addr, =, op_to_addr) 451 | OPCODE 12: // ROL|ROR|RCL|RCR|SHL|SHR|???|SAR reg/mem, 1/CL/imm (80186) 452 | scratch2_uint = SIGN_OF(mem[rm_addr]), 453 | scratch_uint = extra ? // xxx reg/mem, imm 454 | ++reg_ip, 455 | (char)i_data1 456 | : // xxx reg/mem, CL 457 | i_d 458 | ? 31 & regs8[REG_CL] 459 | : // xxx reg/mem, 1 460 | 1; 461 | if (scratch_uint) 462 | { 463 | if (i_reg < 4) // Rotate operations 464 | scratch_uint %= i_reg / 2 + TOP_BIT, 465 | R_M_OP(scratch2_uint, =, mem[rm_addr]); 466 | if (i_reg & 1) // Rotate/shift right operations 467 | R_M_OP(mem[rm_addr], >>=, scratch_uint); 468 | else // Rotate/shift left operations 469 | R_M_OP(mem[rm_addr], <<=, scratch_uint); 470 | if (i_reg > 3) // Shift operations 471 | set_opcode(0x10); // Decode like ADC 472 | if (i_reg > 4) // SHR or SAR 473 | set_CF(op_dest >> (scratch_uint - 1) & 1); 474 | } 475 | 476 | switch (i_reg) 477 | { 478 | OPCODE_CHAIN 0: // ROL 479 | R_M_OP(mem[rm_addr], += , scratch2_uint >> (TOP_BIT - scratch_uint)); 480 | set_OF(SIGN_OF(op_result) ^ set_CF(op_result & 1)) 481 | OPCODE 1: // ROR 482 | scratch2_uint &= (1 << scratch_uint) - 1, 483 | R_M_OP(mem[rm_addr], += , scratch2_uint << (TOP_BIT - scratch_uint)); 484 | set_OF(SIGN_OF(op_result * 2) ^ set_CF(SIGN_OF(op_result))) 485 | OPCODE 2: // RCL 486 | R_M_OP(mem[rm_addr], += (regs8[FLAG_CF] << (scratch_uint - 1)) + , scratch2_uint >> (1 + TOP_BIT - scratch_uint)); 487 | set_OF(SIGN_OF(op_result) ^ set_CF(scratch2_uint & 1 << (TOP_BIT - scratch_uint))) 488 | OPCODE 3: // RCR 489 | R_M_OP(mem[rm_addr], += (regs8[FLAG_CF] << (TOP_BIT - scratch_uint)) + , scratch2_uint << (1 + TOP_BIT - scratch_uint)); 490 | set_CF(scratch2_uint & 1 << (scratch_uint - 1)); 491 | set_OF(SIGN_OF(op_result) ^ SIGN_OF(op_result * 2)) 492 | OPCODE 4: // SHL 493 | set_OF(SIGN_OF(op_result) ^ set_CF(SIGN_OF(op_dest << (scratch_uint - 1)))) 494 | OPCODE 5: // SHR 495 | set_OF(SIGN_OF(op_dest)) 496 | OPCODE 7: // SAR 497 | scratch_uint < TOP_BIT || set_CF(scratch2_uint); 498 | set_OF(0); 499 | R_M_OP(mem[rm_addr], +=, scratch2_uint *= ~(((1 << TOP_BIT) - 1) >> scratch_uint)); 500 | } 501 | OPCODE 13: // LOOPxx|JCZX 502 | scratch_uint = !!--regs16[REG_CX]; 503 | 504 | switch(i_reg4bit) 505 | { 506 | OPCODE_CHAIN 0: // LOOPNZ 507 | scratch_uint &= !regs8[FLAG_ZF] 508 | OPCODE 1: // LOOPZ 509 | scratch_uint &= regs8[FLAG_ZF] 510 | OPCODE 3: // JCXXZ 511 | scratch_uint = !++regs16[REG_CX]; 512 | } 513 | reg_ip += scratch_uint*(char)i_data0 514 | OPCODE 14: // JMP | CALL short/near 515 | reg_ip += 3 - i_d; 516 | if (!i_w) 517 | { 518 | if (i_d) // JMP far 519 | reg_ip = 0, 520 | regs16[REG_CS] = i_data2; 521 | else // CALL 522 | R_M_PUSH(reg_ip); 523 | } 524 | reg_ip += i_d && i_w ? (char)i_data0 : i_data0 525 | OPCODE 15: // TEST reg, r/m 526 | MEM_OP(op_from_addr, &, op_to_addr) 527 | OPCODE 16: // XCHG AX, regs16 528 | i_w = 1; 529 | op_to_addr = REGS_BASE; 530 | op_from_addr = GET_REG_ADDR(i_reg4bit); 531 | OPCODE_CHAIN 24: // NOP|XCHG reg, r/m 532 | if (op_to_addr != op_from_addr) 533 | OP(^=), 534 | MEM_OP(op_from_addr, ^=, op_to_addr), 535 | OP(^=) 536 | OPCODE 17: // MOVSx (extra=0)|STOSx (extra=1)|LODSx (extra=2) 537 | scratch2_uint = seg_override_en ? seg_override : REG_DS; 538 | 539 | for (scratch_uint = rep_override_en ? regs16[REG_CX] : 1; scratch_uint; scratch_uint--) 540 | { 541 | MEM_OP(extra < 2 ? SEGREG(REG_ES, REG_DI,) : REGS_BASE, =, extra & 1 ? REGS_BASE : SEGREG(scratch2_uint, REG_SI,)), 542 | extra & 1 || INDEX_INC(REG_SI), 543 | extra & 2 || INDEX_INC(REG_DI); 544 | } 545 | 546 | if (rep_override_en) 547 | regs16[REG_CX] = 0 548 | OPCODE 18: // CMPSx (extra=0)|SCASx (extra=1) 549 | scratch2_uint = seg_override_en ? seg_override : REG_DS; 550 | 551 | if ((scratch_uint = rep_override_en ? regs16[REG_CX] : 1)) 552 | { 553 | for (; scratch_uint; rep_override_en || scratch_uint--) 554 | { 555 | MEM_OP(extra ? REGS_BASE : SEGREG(scratch2_uint, REG_SI,), -, SEGREG(REG_ES, REG_DI,)), 556 | extra || INDEX_INC(REG_SI), 557 | INDEX_INC(REG_DI), rep_override_en && !(--regs16[REG_CX] && (!op_result == rep_mode)) && (scratch_uint = 0); 558 | } 559 | 560 | set_flags_type = FLAGS_UPDATE_SZP | FLAGS_UPDATE_AO_ARITH; // Funge to set SZP/AO flags 561 | set_CF(op_result > op_dest); 562 | } 563 | OPCODE 19: // RET|RETF|IRET 564 | i_d = i_w; 565 | R_M_POP(reg_ip); 566 | if (extra) // IRET|RETF|RETF imm16 567 | R_M_POP(regs16[REG_CS]); 568 | if (extra & 2) // IRET 569 | set_flags(R_M_POP(scratch_uint)); 570 | else if (!i_d) // RET|RETF imm16 571 | regs16[REG_SP] += i_data0 572 | OPCODE 20: // MOV r/m, immed 573 | R_M_OP(mem[op_from_addr], =, i_data2) 574 | OPCODE 21: // IN AL/AX, DX/imm8 575 | io_ports[0x20] = 0; // PIC EOI 576 | io_ports[0x42] = --io_ports[0x40]; // PIT channel 0/2 read placeholder 577 | io_ports[0x3DA] ^= 9; // CGA refresh 578 | scratch_uint = extra ? regs16[REG_DX] : (unsigned char)i_data0; 579 | scratch_uint == 0x60 && (io_ports[0x64] = 0); // Scancode read flag 580 | 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 581 | R_M_OP(regs8[REG_AL], =, io_ports[scratch_uint]); 582 | OPCODE 22: // OUT DX/imm8, AL/AX 583 | scratch_uint = extra ? regs16[REG_DX] : (unsigned char)i_data0; 584 | R_M_OP(io_ports[scratch_uint], =, regs8[REG_AL]); 585 | scratch_uint == 0x61 && (io_hi_lo = 0, spkr_en |= regs8[REG_AL] & 3); // Speaker control 586 | (scratch_uint == 0x40 || scratch_uint == 0x42) && (io_ports[0x43] & 6) && (mem[0x469 + scratch_uint - (io_hi_lo ^= 1)] = regs8[REG_AL]); // PIT rate programming 587 | #ifndef NO_GRAPHICS 588 | scratch_uint == 0x43 && (io_hi_lo = 0, regs8[REG_AL] >> 6 == 2) && (SDL_PauseAudio((regs8[REG_AL] & 0xF7) != 0xB6), 0); // Speaker enable 589 | #endif 590 | scratch_uint == 0x3D5 && (io_ports[0x3D4] >> 1 == 6) && (mem[0x4AD + !(io_ports[0x3D4] & 1)] = regs8[REG_AL]); // CRT video RAM start offset 591 | 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 592 | scratch_uint == 0x3B5 && io_ports[0x3B4] == 1 && (GRAPHICS_X = regs8[REG_AL] * 16); // Hercules resolution reprogramming. Defaults are set in the BIOS 593 | scratch_uint == 0x3B5 && io_ports[0x3B4] == 6 && (GRAPHICS_Y = regs8[REG_AL] * 4); 594 | OPCODE 23: // REPxx 595 | rep_override_en = 2; 596 | rep_mode = i_w; 597 | seg_override_en && seg_override_en++ 598 | OPCODE 25: // PUSH reg 599 | R_M_PUSH(regs16[extra]) 600 | OPCODE 26: // POP reg 601 | R_M_POP(regs16[extra]) 602 | OPCODE 27: // xS: segment overrides 603 | seg_override_en = 2; 604 | seg_override = extra; 605 | rep_override_en && rep_override_en++ 606 | OPCODE 28: // DAA/DAS 607 | i_w = 0; 608 | extra ? DAA_DAS(-=, >=, 0xFF, 0x99) : DAA_DAS(+=, <, 0xF0, 0x90) // extra = 0 for DAA, 1 for DAS 609 | OPCODE 29: // AAA/AAS 610 | op_result = AAA_AAS(extra - 1) 611 | OPCODE 30: // CBW 612 | regs8[REG_AH] = -SIGN_OF(regs8[REG_AL]) 613 | OPCODE 31: // CWD 614 | regs16[REG_DX] = -SIGN_OF(regs16[REG_AX]) 615 | OPCODE 32: // CALL FAR imm16:imm16 616 | R_M_PUSH(regs16[REG_CS]); 617 | R_M_PUSH(reg_ip + 5); 618 | regs16[REG_CS] = i_data2; 619 | reg_ip = i_data0 620 | OPCODE 33: // PUSHF 621 | make_flags(); 622 | R_M_PUSH(scratch_uint) 623 | OPCODE 34: // POPF 624 | set_flags(R_M_POP(scratch_uint)) 625 | OPCODE 35: // SAHF 626 | make_flags(); 627 | set_flags((scratch_uint & 0xFF00) + regs8[REG_AH]) 628 | OPCODE 36: // LAHF 629 | make_flags(), 630 | regs8[REG_AH] = scratch_uint 631 | OPCODE 37: // LES|LDS reg, r/m 632 | i_w = i_d = 1; 633 | DECODE_RM_REG; 634 | OP(=); 635 | MEM_OP(REGS_BASE + extra, =, rm_addr + 2) 636 | OPCODE 38: // INT 3 637 | ++reg_ip; 638 | pc_interrupt(3) 639 | OPCODE 39: // INT imm8 640 | reg_ip += 2; 641 | pc_interrupt(i_data0) 642 | OPCODE 40: // INTO 643 | ++reg_ip; 644 | regs8[FLAG_OF] && pc_interrupt(4) 645 | OPCODE 41: // AAM 646 | if (i_data0 &= 0xFF) 647 | regs8[REG_AH] = regs8[REG_AL] / i_data0, 648 | op_result = regs8[REG_AL] %= i_data0; 649 | else // Divide by zero 650 | pc_interrupt(0) 651 | OPCODE 42: // AAD 652 | i_w = 0; 653 | regs16[REG_AX] = op_result = 0xFF & regs8[REG_AL] + i_data0 * regs8[REG_AH] 654 | OPCODE 43: // SALC 655 | regs8[REG_AL] = -regs8[FLAG_CF] 656 | OPCODE 44: // XLAT 657 | regs8[REG_AL] = mem[SEGREG(seg_override_en ? seg_override : REG_DS, REG_BX, regs8[REG_AL] +)] 658 | OPCODE 45: // CMC 659 | regs8[FLAG_CF] ^= 1 660 | OPCODE 46: // CLC|STC|CLI|STI|CLD|STD 661 | regs8[extra / 2] = extra & 1 662 | OPCODE 47: // TEST AL/AX, immed 663 | R_M_OP(regs8[REG_AL], &, i_data0) 664 | OPCODE 48: // Emulator-specific 0F xx opcodes 665 | switch ((char)i_data0) 666 | { 667 | OPCODE_CHAIN 0: // PUTCHAR_AL 668 | write(1, regs8, 1) 669 | OPCODE 1: // GET_RTC 670 | time(&clock_buf); 671 | ftime(&ms_clock); 672 | memcpy(mem + SEGREG(REG_ES, REG_BX,), localtime(&clock_buf), sizeof(struct tm)); 673 | CAST(short)mem[SEGREG(REG_ES, REG_BX, 36+)] = ms_clock.millitm; 674 | OPCODE 2: // DISK_READ 675 | OPCODE_CHAIN 3: // DISK_WRITE 676 | regs8[REG_AL] = ~lseek(disk[regs8[REG_DL]], CAST(unsigned)regs16[REG_BP] << 9, 0) 677 | ? ((char)i_data0 == 3 ? (int(*)())write : (int(*)())read)(disk[regs8[REG_DL]], mem + SEGREG(REG_ES, REG_BX,), regs16[REG_AX]) 678 | : 0; 679 | } 680 | } 681 | 682 | // Increment instruction pointer by computed instruction length. Tables in the BIOS binary 683 | // help us here. 684 | 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); 685 | 686 | // If instruction needs to update SF, ZF and PF, set them as appropriate 687 | if (set_flags_type & FLAGS_UPDATE_SZP) 688 | { 689 | regs8[FLAG_SF] = SIGN_OF(op_result); 690 | regs8[FLAG_ZF] = !op_result; 691 | regs8[FLAG_PF] = bios_table_lookup[TABLE_PARITY_FLAG][(unsigned char)op_result]; 692 | 693 | // If instruction is an arithmetic or logic operation, also set AF/OF/CF as appropriate. 694 | if (set_flags_type & FLAGS_UPDATE_AO_ARITH) 695 | set_AF_OF_arith(); 696 | if (set_flags_type & FLAGS_UPDATE_OC_LOGIC) 697 | set_CF(0), set_OF(0); 698 | } 699 | 700 | // Poll timer/keyboard every KEYBOARD_TIMER_UPDATE_DELAY instructions 701 | if (!(++inst_counter % KEYBOARD_TIMER_UPDATE_DELAY)) 702 | int8_asap = 1; 703 | 704 | #ifndef NO_GRAPHICS 705 | // Update the video graphics display every GRAPHICS_UPDATE_DELAY instructions 706 | if (!(inst_counter % GRAPHICS_UPDATE_DELAY)) 707 | { 708 | // Video card in graphics mode? 709 | if (io_ports[0x3B8] & 2) 710 | { 711 | // If we don't already have an SDL window open, set it up and compute color and video memory translation tables 712 | if (!sdl_screen) 713 | { 714 | for (int i = 0; i < 16; i++) 715 | pixel_colors[i] = mem[0x4AC] ? // CGA? 716 | cga_colors[(i & 12) >> 2] + (cga_colors[i & 3] << 16) // CGA -> RGB332 717 | : 0xFF*(((i & 1) << 24) + ((i & 2) << 15) + ((i & 4) << 6) + ((i & 8) >> 3)); // Hercules -> RGB332 718 | 719 | for (int i = 0; i < GRAPHICS_X * GRAPHICS_Y / 4; i++) 720 | 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); 721 | 722 | SDL_Init(SDL_INIT_VIDEO); 723 | sdl_screen = SDL_SetVideoMode(GRAPHICS_X, GRAPHICS_Y, 8, 0); 724 | SDL_EnableUNICODE(1); 725 | SDL_EnableKeyRepeat(500, 30); 726 | } 727 | 728 | // Refresh SDL display from emulated graphics card video RAM 729 | 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 730 | for (int i = 0; i < GRAPHICS_X * GRAPHICS_Y / 4; i++) 731 | ((unsigned *)sdl_screen->pixels)[i] = pixel_colors[15 & (vid_mem_base[vid_addr_lookup[i]] >> 4*!(i & 1))]; 732 | 733 | SDL_Flip(sdl_screen); 734 | } 735 | else if (sdl_screen) // Application has gone back to text mode, so close the SDL window 736 | { 737 | SDL_QuitSubSystem(SDL_INIT_VIDEO); 738 | sdl_screen = 0; 739 | } 740 | SDL_PumpEvents(); 741 | } 742 | #endif 743 | 744 | // Application has set trap flag, so fire INT 1 745 | if (trap_flag) 746 | pc_interrupt(1); 747 | 748 | trap_flag = regs8[FLAG_TF]; 749 | 750 | // If a timer tick is pending, interrupts are enabled, and no overrides/REP are active, 751 | // then process the tick and check for new keystrokes 752 | if (int8_asap && !seg_override_en && !rep_override_en && regs8[FLAG_IF] && !regs8[FLAG_TF]) 753 | pc_interrupt(0xA), int8_asap = 0, SDL_KEYBOARD_DRIVER; 754 | } 755 | 756 | #ifndef NO_GRAPHICS 757 | SDL_Quit(); 758 | #endif 759 | return 0; 760 | } 761 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 8086tiny: a tiny, highly functional, highly portable PC emulator/VM 2 | # Copyright 2013-14, Adrian Cable (adrian.cable@gmail.com) - http://www.megalith.co.uk/8086tiny 3 | # 4 | # This work is licensed under the MIT License. See included LICENSE.TXT. 5 | 6 | # 8086tiny builds with graphics and sound support 7 | # 8086tiny_slowcpu improves graphics performance on slow platforms (e.g. Raspberry Pi) 8 | # no_graphics compiles without SDL graphics/sound 9 | 10 | OPTS_ALL=-O3 -fsigned-char -std=c99 11 | OPTS_SDL=`sdl-config --cflags --libs` 12 | OPTS_NOGFX=-DNO_GRAPHICS 13 | OPTS_SLOWCPU=-DGRAPHICS_UPDATE_DELAY=25000 14 | 15 | 8086tiny: 8086tiny.c 16 | ${CC} 8086tiny.c ${OPTS_SDL} ${OPTS_ALL} -o 8086tiny 17 | strip 8086tiny 18 | 19 | 8086tiny_slowcpu: 8086tiny.c 20 | ${CC} 8086tiny.c ${OPTS_SDL} ${OPTS_ALL} ${OPTS_SLOWCPU} -o 8086tiny 21 | strip 8086tiny 22 | 23 | no_graphics: 8086tiny.c 24 | ${CC} 8086tiny.c ${OPTS_NOGFX} ${OPTS_ALL} -o 8086tiny 25 | strip 8086tiny 26 | 27 | clean: 28 | rm 8086tiny 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 8086tiny 2 | ======== 3 | 4 | ![QNX 2.15C in action](docs/images/QNX2_8086tiny.png) 5 | 6 | 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. 7 | 8 | 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). 9 | 10 | 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! 11 | 12 | Any questions, comments or suggestions are very welcome in our forum at 8086tiny.freeforums.net. 13 | 14 | BIOS modifications 15 | ================== 16 | 17 | QNX 2 harddisk images with 306x4x17 geometry are floating around. With the bios enhancement (that checks for small sized disk images that might be cyls./4/17) can be booted fine. 18 | -------------------------------------------------------------------------------- /bios: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retrohun/8086tiny/8c9a82627d5c054ed6b84ce3e349a458b19b0c6c/bios -------------------------------------------------------------------------------- /bios_source/bios.asm: -------------------------------------------------------------------------------- 1 | ; BIOS source for 8086tiny IBM PC emulator (revision 1.21 and above). Compiles with NASM. 2 | ; Copyright 2013-14, Adrian Cable (adrian.cable@gmail.com) - http://www.megalith.co.uk/8086tiny 3 | ; 4 | ; Revision 1.61 5 | ; 6 | ; This work is licensed under the MIT License. See included LICENSE.TXT. 7 | 8 | cpu 8086 9 | 10 | ; Here we define macros for some custom instructions that help the emulator talk with the outside 11 | ; world. They are described in detail in the hint.html file, which forms part of the emulator 12 | ; distribution. 13 | 14 | %macro extended_putchar_al 0 15 | db 0x0f, 0x00 16 | %endmacro 17 | 18 | %macro extended_get_rtc 0 19 | db 0x0f, 0x01 20 | %endmacro 21 | 22 | %macro extended_read_disk 0 23 | db 0x0f, 0x02 24 | %endmacro 25 | 26 | %macro extended_write_disk 0 27 | db 0x0f, 0x03 28 | %endmacro 29 | 30 | org 100h ; BIOS loads at offset 0x0100 31 | 32 | main: 33 | 34 | jmp bios_entry 35 | 36 | ; Here go pointers to the different data tables used for instruction decoding 37 | 38 | dw rm_mode12_reg1 ; Table 0: R/M mode 1/2 "register 1" lookup 39 | dw rm_mode012_reg2 ; Table 1: R/M mode 1/2 "register 2" lookup 40 | dw rm_mode12_disp ; Table 2: R/M mode 1/2 "DISP multiplier" lookup 41 | dw rm_mode12_dfseg ; Table 3: R/M mode 1/2 "default segment" lookup 42 | dw rm_mode0_reg1 ; Table 4: R/M mode 0 "register 1" lookup 43 | dw rm_mode012_reg2 ; Table 5: R/M mode 0 "register 2" lookup 44 | dw rm_mode0_disp ; Table 6: R/M mode 0 "DISP multiplier" lookup 45 | dw rm_mode0_dfseg ; Table 7: R/M mode 0 "default segment" lookup 46 | dw xlat_ids ; Table 8: Translation of raw opcode index ("Raw ID") to function number ("Xlat'd ID") 47 | dw ex_data ; Table 9: Translation of Raw ID to Extra Data 48 | 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) 49 | dw parity ; Table 11: Parity flag loop-up table (256 entries) 50 | dw base_size ; Table 12: Translation of Raw ID to base instruction size (bytes) 51 | dw i_w_adder ; Table 13: Translation of Raw ID to i_w size adder yes/no 52 | dw i_mod_adder ; Table 14: Translation of Raw ID to i_mod size adder yes/no 53 | dw jxx_dec_a ; Table 15: Jxx decode table A 54 | dw jxx_dec_b ; Table 16: Jxx decode table B 55 | dw jxx_dec_c ; Table 17: Jxx decode table C 56 | dw jxx_dec_d ; Table 18: Jxx decode table D 57 | dw flags_mult ; Table 19: FLAGS multipliers 58 | 59 | ; These values (BIOS ID string, BIOS date and so forth) go at the very top of memory 60 | 61 | biosstr db '8086tiny BIOS Revision 1.61!', 0, 0 ; Why not? 62 | mem_top db 0xea, 0, 0x01, 0, 0xf0, '03/08/14', 0, 0xfe, 0 63 | 64 | bios_entry: 65 | 66 | ; Set up initial stack to F000:F000 67 | 68 | mov sp, 0xf000 69 | mov ss, sp 70 | 71 | push cs 72 | pop es 73 | 74 | push ax 75 | 76 | ; The emulator requires a few control registers in memory to always be zero for correct 77 | ; instruction decoding (in particular, register look-up operations). These are the 78 | ; emulator's zero segment (ZS) and always-zero flag (XF). Because the emulated memory 79 | ; space is uninitialised, we need to be sure these values are zero before doing anything 80 | ; else. The instructions we need to use to set them must not rely on look-up operations. 81 | ; So e.g. MOV to memory is out but string operations are fine. 82 | 83 | cld 84 | 85 | xor ax, ax 86 | mov di, 24 87 | stosw ; Set ZS = 0 88 | mov di, 49 89 | stosb ; Set XF = 0 90 | 91 | ; Now we can do whatever we want! DL starts off being the boot disk. 92 | 93 | mov [cs:boot_device], dl 94 | 95 | ; Set up Hercules graphics support. We start with the adapter in text mode 96 | 97 | push dx 98 | 99 | mov dx, 0x3b8 100 | mov al, 0 101 | out dx, al ; Set Hercules support to text mode 102 | 103 | mov dx, 0x3b4 104 | mov al, 1 ; Hercules CRTC "horizontal displayed" register select 105 | out dx, al 106 | mov dx, 0x3b5 107 | mov al, 0x2d ; 0x2D = 45 (* 16) = 720 pixels wide (GRAPHICS_X) 108 | out dx, al 109 | mov dx, 0x3b4 110 | mov al, 6 ; Hercules CRTC "vertical displayed" register select 111 | out dx, al 112 | mov dx, 0x3b5 113 | mov al, 0x57 ; 0x57 = 87 (* 4) = 348 pixels high (GRAPHICS_Y) 114 | out dx, al 115 | 116 | pop dx 117 | 118 | pop ax 119 | 120 | ; Check cold boot/warm boot. We initialise disk parameters on cold boot only 121 | 122 | cmp byte [cs:boot_state], 0 ; Cold boot? 123 | jne boot 124 | 125 | mov byte [cs:boot_state], 1 ; Set flag so next boot will be warm boot 126 | 127 | ; First, set up the disk subsystem. Only do this on the very first startup, when 128 | ; the emulator sets up the CX/AX registers with disk information. 129 | 130 | ; Compute the cylinder/head/sector count for the HD disk image, if present. 131 | ; Total number of sectors is in CX:AX, or 0 if there is no HD image. First, 132 | ; we put it in DX:CX. 133 | 134 | mov [cs:hd_secs_hi], cx 135 | mov [cs:hd_secs_lo], ax 136 | 137 | mov dx, cx 138 | mov cx, ax 139 | 140 | mov word [cs:num_disks], 1 141 | or ax, dx 142 | jz calc_hd 143 | mov word [cs:num_disks], 2 144 | 145 | calc_hd: 146 | 147 | mov ax, cx 148 | mov word [cs:hd_max_track], 1 149 | mov word [cs:hd_max_head], 1 150 | 151 | cmp dx, 0 ; More than 63 total sectors? If so, we have more than 1 track. 152 | ja sect_overflow 153 | cmp ax, 63 154 | ja sect_oldhdd 155 | 156 | mov [cs:hd_max_sector], ax 157 | jmp calc_heads 158 | 159 | sect_oldhdd: 160 | 161 | mov cx, 68 ; testing of possible HEAD*SPT divisor of total number of sectors 162 | div cx 163 | or dx, dx ; test if the remainder is zero 164 | jnz not_old 165 | test ax, 0x3C00 166 | jz oldhdd_true 167 | 168 | not_old: 169 | 170 | mov dx, [cs:hd_secs_hi] 171 | mov cx, [cs:hd_secs_lo] 172 | jmp sect_overflow 173 | 174 | oldhdd_true: 175 | 176 | mov [cs:hd_max_track], ax 177 | mov word [cs:hd_max_head], 4 178 | mov word [cs:hd_max_sector], 17 179 | jmp calc_end 180 | 181 | sect_overflow: 182 | 183 | mov cx, 63 ; Calculate number of tracks 184 | div cx 185 | mov [cs:hd_max_track], ax 186 | mov word [cs:hd_max_sector], 63 187 | 188 | calc_heads: 189 | 190 | mov dx, 0 ; More than 1024 tracks? If so, we have more than 1 head. 191 | mov ax, [cs:hd_max_track] 192 | cmp ax, 1024 193 | ja track_overflow 194 | 195 | jmp calc_end 196 | 197 | track_overflow: 198 | 199 | mov cx, 1024 200 | div cx 201 | mov [cs:hd_max_head], ax 202 | mov word [cs:hd_max_track], 1024 203 | 204 | calc_end: 205 | 206 | ; Convert number of tracks into maximum track (0-based) and then store in INT 41 207 | ; HD parameter table 208 | 209 | mov ax, [cs:hd_max_head] 210 | mov [cs:int41_max_heads], al 211 | mov ax, [cs:hd_max_track] 212 | mov [cs:int41_max_cyls], ax 213 | mov ax, [cs:hd_max_sector] 214 | mov [cs:int41_max_sect], al 215 | 216 | dec word [cs:hd_max_track] 217 | dec word [cs:hd_max_head] 218 | 219 | ; Main BIOS entry point. Zero the flags, and set up registers. 220 | 221 | boot: mov ax, 0 222 | push ax 223 | popf 224 | 225 | push cs 226 | push cs 227 | pop ds 228 | pop ss 229 | mov sp, 0xf000 230 | 231 | ; Set up the IVT. First we zero out the table 232 | 233 | cld 234 | 235 | xor ax, ax 236 | mov es, ax 237 | xor di, di 238 | mov cx, 512 239 | rep stosw 240 | 241 | ; Then we load in the pointers to our interrupt handlers 242 | 243 | mov di, 0 244 | mov si, int_table 245 | mov cx, [itbl_size] 246 | rep movsb 247 | 248 | ; Set pointer to INT 41 table for hard disk 249 | 250 | mov cx, int41 251 | mov word [es:4*0x41], cx 252 | mov cx, 0xf000 253 | mov word [es:4*0x41 + 2], cx 254 | 255 | ; Set up last 16 bytes of memory, including boot jump, BIOS date, machine ID byte 256 | 257 | mov ax, 0xffff 258 | mov es, ax 259 | mov di, 0 260 | mov si, mem_top 261 | mov cx, 16 262 | rep movsb 263 | 264 | ; Set up the BIOS data area 265 | 266 | mov ax, 0x40 267 | mov es, ax 268 | mov di, 0 269 | mov si, bios_data 270 | mov cx, 0x100 271 | rep movsb 272 | 273 | ; Clear video memory 274 | 275 | mov ax, 0xb800 276 | mov es, ax 277 | mov di, 0 278 | mov cx, 80*25 279 | mov ax, 0x0700 280 | rep stosw 281 | 282 | ; Clear video memory shadow buffer 283 | 284 | mov ax, 0xc800 285 | mov es, ax 286 | mov di, 0 287 | mov cx, 80*25 288 | mov ax, 0x0700 289 | rep stosw 290 | 291 | ; Set up some I/O ports, between 0 and FFF. Most of them we set to 0xFF, to indicate no device present 292 | 293 | mov dx, 0x61 294 | mov al, 0 295 | out dx, al ; Make sure the speaker is off 296 | 297 | mov dx, 0x60 298 | out dx, al ; No scancode 299 | 300 | mov dx, 0x64 301 | out dx, al ; No key waiting 302 | 303 | mov dx, 0 304 | mov al, 0xFF 305 | 306 | next_out: 307 | 308 | inc dx 309 | 310 | cmp dx, 0x40 ; We deal with the PIT channel 0 later 311 | je next_out 312 | cmp dx, 0x42 ; We deal with the PIT channel 2 later 313 | je next_out 314 | cmp dx, 0x3B8 ; We deal with the Hercules port later, too 315 | je next_out 316 | cmp dx, 0x60 ; Keyboard scancode 317 | je next_out 318 | cmp dx, 0x61 ; Sound output 319 | je next_out 320 | cmp dx, 0x64 ; Keyboard status 321 | je next_out 322 | 323 | out dx, al 324 | 325 | cmp dx, 0xFFF 326 | jl next_out 327 | 328 | mov al, 0 329 | 330 | mov dx, 0x3DA ; CGA refresh port 331 | out dx, al 332 | 333 | mov dx, 0x3BA ; Hercules detection port 334 | out dx, al 335 | 336 | mov dx, 0x3B8 ; Hercules video mode port 337 | out dx, al 338 | 339 | mov dx, 0x3BC ; LPT1 340 | out dx, al 341 | 342 | mov dx, 0x62 ; PPI - needed for memory parity checks 343 | out dx, al 344 | 345 | ; Get initial RTC value 346 | 347 | push cs 348 | pop es 349 | mov bx, timetable 350 | extended_get_rtc 351 | mov ax, [es:tm_msec] 352 | mov [cs:last_int8_msec], ax 353 | 354 | ; Read boot sector from FDD, and load it into 0:7C00 355 | 356 | mov ax, 0 357 | mov es, ax 358 | 359 | mov ax, 0x0201 360 | mov dh, 0 361 | mov dl, [cs:boot_device] 362 | mov cx, 1 363 | mov bx, 0x7c00 364 | int 13h 365 | 366 | ; Jump to boot sector 367 | 368 | jmp 0:0x7c00 369 | 370 | ; ************************* INT 7h handler - keyboard driver (8086tiny internal) 371 | 372 | int7: ; Whenever the user presses a key, INT 7 is called by the emulator. 373 | ; ASCII character of the keystroke is at 0040:this_keystroke 374 | 375 | push ds 376 | push es 377 | push ax 378 | push bx 379 | push bp 380 | 381 | push cs 382 | pop ds 383 | 384 | mov bx, 0x40 ; Set segment to BIOS data area segment (0x40) 385 | mov es, bx 386 | 387 | ; Retrieve the keystroke 388 | 389 | mov ax, [es:this_keystroke-bios_data] 390 | mov byte [es:this_keystroke+1-bios_data], 0 391 | 392 | real_key: 393 | 394 | mov byte [cs:last_key_sdl], 0 395 | 396 | test ah, 4 ; This key doesn't come from SDL 397 | jz check_linux_bksp 398 | 399 | mov byte [es:keyflags1-bios_data], 0 400 | mov byte [es:keyflags2-bios_data], 0 401 | 402 | mov byte [cs:last_key_sdl], 1 ; Key down from SDL 403 | 404 | test ah, 0x40 ; Key up 405 | jz sdl_check_specials 406 | 407 | mov byte [cs:last_key_sdl], 2 ; Key up from SDL 408 | 409 | sdl_check_specials: 410 | 411 | mov bx, ax 412 | and bh, 7 ; If key is between 52F and 534 (Shift/Ctrl/Alt), ignore the key state flags 413 | cmp bx, 0x52f 414 | je sdl_just_press_shift 415 | cmp bx, 0x530 416 | je sdl_just_press_shift 417 | cmp bx, 0x533 418 | je sdl_just_press_alt 419 | cmp bx, 0x534 420 | je sdl_just_press_alt 421 | cmp bx, 0x531 422 | je sdl_just_press_ctrl 423 | cmp bx, 0x532 424 | je sdl_just_press_ctrl 425 | jmp sdl_check_alt 426 | 427 | sdl_just_press_shift: 428 | 429 | mov al, 0x36 ; Shift 430 | and ah, 0x40 ; Key up? 431 | add al, ah 432 | add al, ah 433 | call io_key_available 434 | jmp i2_dne 435 | 436 | sdl_just_press_alt: 437 | 438 | mov al, 0x38 ; Alt 439 | and ah, 0x40 ; Key up? 440 | add al, ah 441 | add al, ah 442 | call io_key_available 443 | jmp i2_dne 444 | 445 | sdl_just_press_ctrl: 446 | 447 | mov al, 0x1d ; Ctrl 448 | and ah, 0x40 ; Key up? 449 | add al, ah 450 | add al, ah 451 | call io_key_available 452 | jmp i2_dne 453 | 454 | sdl_check_alt: 455 | 456 | test ah, 8 ; Alt+something? 457 | jz sdl_no_alt 458 | add byte [es:keyflags1-bios_data], 8 459 | add byte [es:keyflags2-bios_data], 2 460 | 461 | sdl_no_alt: 462 | 463 | test ah, 0x20 ; Ctrl+something? 464 | jz sdl_no_ctrl 465 | add byte [es:keyflags1-bios_data], 4 466 | 467 | sdl_no_ctrl: 468 | 469 | test ah, 0x10 ; Shift+something? 470 | jz sdl_no_mods 471 | add byte [es:keyflags1-bios_data], 1 472 | 473 | sdl_no_mods: 474 | 475 | and ah, 1 ; We have processed all SDL modifiers, so remove them 476 | 477 | ;cmp ax, 160 ; Alt+Space? 478 | ;jne next_sdl_alt_keys 479 | ;mov al, ' ' 480 | ;mov byte [es:this_keystroke-bios_data], al 481 | 482 | check_sdl_f_keys: 483 | 484 | cmp ax, 0x125 485 | ja i2_dne ; Unknown key 486 | 487 | cmp ax, 0x11a 488 | jb check_sdl_pgup_pgdn_keys 489 | 490 | sub ax, 0xdf ; F1 - F10 491 | cmp ax, 0x45 492 | jb check_sdl_f_keys2 493 | add ax, 0x12 ; F11 - F12 494 | 495 | check_sdl_f_keys2: 496 | 497 | mov bh, al 498 | mov al, 0 499 | jmp sdl_scancode_xlat_done 500 | 501 | check_sdl_pgup_pgdn_keys: 502 | 503 | cmp ax, 0x116 504 | jb check_sdl_cursor_keys 505 | cmp ax, 0x119 506 | ja check_sdl_cursor_keys 507 | 508 | sub ax, 0x116 509 | mov bx, pgup_pgdn_xlt 510 | cs xlat 511 | 512 | mov bh, al 513 | mov al, 0 514 | jmp sdl_scancode_xlat_done 515 | 516 | check_sdl_cursor_keys: 517 | 518 | cmp ax, 0x111 ; SDL cursor keys 519 | jb sdl_process_key ; No special handling for other keys yet 520 | 521 | sub ax, 0x111 522 | mov bx, unix_cursor_xlt 523 | xlat ; Convert SDL cursor keys to scancode 524 | 525 | mov bh, al 526 | mov al, 0 527 | mov byte [es:this_keystroke-bios_data], 0 528 | jmp sdl_scancode_xlat_done 529 | 530 | sdl_process_key: 531 | 532 | cmp ax, 0x100 533 | jae i2_dne ; Unsupported key 534 | cmp al, 0x7f ; SDL 0x7F backspace? Convert to 0x08 535 | jne sdl_process_key2 536 | mov al, 8 537 | 538 | sdl_process_key2: 539 | 540 | push ax 541 | mov bx, a2scan_tbl ; ASCII to scancode table 542 | xlat 543 | mov bh, al 544 | pop ax ; Scancode in BH, keycode in AL 545 | 546 | sdl_scancode_xlat_done: 547 | 548 | add bh, 0x80 ; Key up scancode 549 | cmp byte [cs:last_key_sdl], 2 ; Key up? 550 | je sdl_not_in_buf 551 | 552 | sub bh, 0x80 ; Key down scancode 553 | 554 | sdl_key_down: 555 | 556 | mov [es:this_keystroke-bios_data], al 557 | 558 | sdl_not_in_buf: 559 | 560 | mov al, bh 561 | call io_key_available 562 | jmp i2_dne 563 | 564 | check_linux_bksp: 565 | 566 | cmp al, 0 ; Null keystroke - ignore 567 | je i2_dne 568 | 569 | cmp al, 0x7f ; Linux code for backspace - change to 8 570 | jne after_check_bksp 571 | 572 | mov al, 8 573 | mov byte [es:this_keystroke-bios_data], 8 574 | 575 | after_check_bksp: 576 | 577 | 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) 578 | je i2_n 579 | 580 | cmp al, 0x01 ; Ctrl+A pressed - this is the sequence for "next key is Alt+" 581 | jne i2_not_alt 582 | 583 | mov byte [es:keyflags1-bios_data], 8 ; Alt flag down 584 | mov byte [es:keyflags2-bios_data], 2 ; Alt flag down 585 | mov al, 0x38 ; Simulated Alt by Ctrl+A prefix? 586 | call io_key_available 587 | 588 | mov byte [es:next_key_alt-bios_data], 1 589 | jmp i2_dne 590 | 591 | i2_not_alt: 592 | 593 | cmp al, 0x06 ; Ctrl+F pressed - this is the sequence for "next key is Fxx" 594 | jne i2_not_fn 595 | 596 | mov byte [es:next_key_fn-bios_data], 1 597 | jmp i2_dne 598 | 599 | i2_not_fn: 600 | 601 | cmp byte [es:notranslate_flg-bios_data], 1 ; If no translation mode is on, just pass through the scan code. ASCII key is zero. 602 | mov byte [es:notranslate_flg-bios_data], 0 603 | jne need_to_translate 604 | 605 | mov byte [es:this_keystroke-bios_data], 0 606 | jmp after_translate 607 | 608 | need_to_translate: 609 | 610 | 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. 611 | mov byte [es:notranslate_flg-bios_data], 1 612 | je i2_dne ; Don't add the 0xE0 to the keyboard buffer 613 | 614 | mov byte [es:notranslate_flg-bios_data], 0 615 | 616 | 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 617 | jne i2_escnext 618 | 619 | ; If the last key pressed was ESC, then we need to stuff it 620 | cmp byte [es:escape_flag-bios_data], 1 621 | jne i2_sf 622 | 623 | ; Stuff an ESC character 624 | 625 | mov byte [es:this_keystroke-bios_data], 0x1b 626 | 627 | mov al, 0x01 628 | call keypress_release 629 | 630 | i2_sf: 631 | 632 | mov byte [es:escape_flag-bios_data], 1 633 | jmp i2_dne 634 | 635 | i2_escnext: 636 | 637 | ; Check if the last key was an escape character 638 | cmp byte [es:escape_flag-bios_data], 1 639 | jne i2_noesc 640 | 641 | ; It is, so check if this key is a [ control character 642 | cmp al, '[' ; [ key pressed 643 | je i2_esc 644 | 645 | ; It isn't, so stuff an ESC character plus this key 646 | 647 | mov byte [es:this_keystroke-bios_data], 0x1b 648 | 649 | mov al, 0x01 650 | call keypress_release 651 | 652 | ; Now actually process this key 653 | mov byte [es:escape_flag-bios_data], 0 654 | mov al, [es:this_keystroke-bios_data] 655 | jmp i2_noesc 656 | 657 | i2_esc: 658 | 659 | ; Last + this characters are ESC ] - do nothing now, but set escape flag 660 | mov byte [es:escape_flag-bios_data], 2 661 | jmp i2_dne 662 | 663 | i2_noesc: 664 | 665 | cmp byte [es:escape_flag-bios_data], 2 666 | jne i2_regular_key 667 | 668 | ; No shifts or Alt for cursor keys 669 | mov byte [es:keyflags1-bios_data], 0 670 | mov byte [es:keyflags2-bios_data], 0 671 | 672 | ; Last + this characters are ESC ] xxx - cursor key, so translate and stuff it 673 | sub al, 'A' 674 | mov bx, unix_cursor_xlt 675 | xlat 676 | 677 | mov byte [es:this_keystroke-bios_data], 0 678 | jmp after_translate 679 | 680 | i2_regular_key: 681 | 682 | mov byte [es:notranslate_flg-bios_data], 0 683 | 684 | mov bx, a2shift_tbl ; ASCII to shift code table 685 | xlat 686 | 687 | ; Now, BL is 1 if shift is down, 0 otherwise. If shift is down, put a shift down scan code 688 | ; in port 0x60. Then call int 9. Otherwise, put a shift up scan code in, and call int 9. 689 | 690 | push ax 691 | 692 | ; Put shift flags in BIOS, 0040:0017. Add 8 to shift flags if Alt is down. 693 | mov ah, [es:next_key_alt-bios_data] 694 | cpu 186 695 | shl ah, 3 696 | cpu 8086 697 | add al, ah 698 | 699 | cmp byte [es:this_keystroke-bios_data], 0x1A ; Ctrl+A to Ctrl+Z? Then add Ctrl to BIOS key flags 700 | ja i2_no_ctrl 701 | cmp byte [es:this_keystroke-bios_data], 0 702 | je i2_no_ctrl 703 | cmp byte [es:this_keystroke-bios_data], 0xD ; CR 704 | je i2_no_ctrl 705 | cmp byte [es:this_keystroke-bios_data], 0xA ; LF 706 | je i2_no_ctrl 707 | cmp byte [es:this_keystroke-bios_data], 0x8 ; Backspace 708 | je i2_no_ctrl 709 | cmp byte [es:this_keystroke-bios_data], 0x9 ; Tab 710 | je i2_no_ctrl 711 | add al, 4 ; Ctrl in key flags 712 | 713 | push ax 714 | mov al, 0x1d ; Ctrl key down 715 | call io_key_available 716 | pop ax 717 | 718 | i2_no_ctrl: 719 | 720 | mov [es:keyflags1-bios_data], al 721 | 722 | cpu 186 723 | shr ah, 2 724 | cpu 8086 725 | mov [es:keyflags2-bios_data], ah 726 | 727 | pop ax 728 | 729 | test al, 1 ; Shift down? 730 | jz i2_n 731 | 732 | mov al, 0x36 ; Right shift down 733 | call io_key_available 734 | 735 | i2_n: 736 | 737 | mov al, [es:this_keystroke-bios_data] 738 | 739 | mov bx, a2scan_tbl ; ASCII to scan code table 740 | xlat 741 | 742 | cmp byte [es:next_key_fn-bios_data], 1 ; Fxx? 743 | jne after_translate 744 | 745 | cmp byte [es:this_keystroke-bios_data], 1 ; Ctrl+F then Ctrl+A outputs code for Ctrl+A 746 | je after_translate 747 | 748 | cmp byte [es:this_keystroke-bios_data], 6 ; Ctrl+F then Ctrl+F outputs code for Ctrl+F 749 | je after_translate 750 | 751 | mov byte [es:this_keystroke-bios_data], 0 ; Fxx key, so zero out ASCII code 752 | add al, 0x39 753 | 754 | after_translate: 755 | 756 | mov byte [es:escape_flag-bios_data], 0 757 | mov byte [es:escape_flag_last-bios_data], 0 758 | 759 | ; If the key is actually an Alt+ key we use an ASCII code of 0 instead of the real value. 760 | 761 | cmp byte [es:next_key_alt-bios_data], 1 762 | jne skip_ascii_zero 763 | 764 | mov byte [es:this_keystroke-bios_data], 0 765 | 766 | skip_ascii_zero: 767 | 768 | ; Output key down/key up event (scancode in AL) to keyboard port 769 | call keypress_release 770 | 771 | ; If scan code is not 0xE0, then also send right shift up if necessary 772 | cmp al, 0xe0 773 | je i2_dne 774 | 775 | test byte [es:keyflags1-bios_data], 1 776 | jz check_ctrl 777 | 778 | mov al, 0xb6 ; Right shift up 779 | call io_key_available 780 | 781 | check_ctrl: 782 | 783 | test byte [es:keyflags1-bios_data], 4 784 | jz check_alt 785 | 786 | mov al, 0x9d ; Right Ctrl up 787 | call io_key_available 788 | 789 | check_alt: 790 | 791 | mov al, byte [es:next_key_alt-bios_data] 792 | mov byte [es:next_key_alt-bios_data], 0 793 | mov byte [es:next_key_fn-bios_data], 0 794 | 795 | cmp al, 1 796 | je endalt 797 | 798 | jmp i2_dne 799 | 800 | endalt: 801 | 802 | mov al, 0xb8 ; Left Alt up 803 | call io_key_available 804 | 805 | i2_dne: 806 | 807 | pop bp 808 | pop bx 809 | pop ax 810 | pop es 811 | pop ds 812 | iret 813 | 814 | ; ************************* INT 9h handler - keyboard (PC BIOS standard) 815 | 816 | int9: 817 | 818 | push es 819 | push ax 820 | push bx 821 | push bp 822 | 823 | in al, 0x60 824 | 825 | cmp al, 0x80 ; Key up? 826 | jae no_add_buf 827 | cmp al, 0x36 ; Shift? 828 | je no_add_buf 829 | cmp al, 0x38 ; Alt? 830 | je no_add_buf 831 | cmp al, 0x1d ; Ctrl? 832 | je no_add_buf 833 | 834 | mov bx, 0x40 835 | mov es, bx 836 | 837 | mov bh, al 838 | mov al, [es:this_keystroke-bios_data] 839 | 840 | ; Tail of the BIOS keyboard buffer goes in BP. This is where we add new keystrokes 841 | 842 | mov bp, [es:kbbuf_tail-bios_data] 843 | mov byte [es:bp], al ; ASCII code 844 | mov byte [es:bp+1], bh ; Scan code 845 | 846 | ; ESC keystroke is in the buffer now 847 | add word [es:kbbuf_tail-bios_data], 2 848 | call kb_adjust_buf ; Wrap the tail around the head if the buffer gets too large 849 | 850 | no_add_buf: 851 | 852 | mov al, 1 853 | out 0x64, al 854 | 855 | pop bp 856 | pop bx 857 | pop ax 858 | pop es 859 | 860 | iret 861 | 862 | ; ************************* INT Ah handler - timer (8086tiny internal) 863 | 864 | inta: 865 | ; 8086tiny called interrupt 0xA frequently, at a rate dependent on the speed of your computer. 866 | ; This interrupt handler scales down the call rate and calls INT 8 at 18.2 times per second, 867 | ; as per a real PC. 868 | 869 | ; See if there is an ESC waiting from a previous INT 7h. If so, put it in the keyboard buffer 870 | ; (because by now - 1/18.2 secs on - we know it can't be part of an escape key sequence). 871 | ; Also handle CGA refresh register. Also release any keys that are still marked as down. 872 | 873 | push ax 874 | push bx 875 | push dx 876 | push bp 877 | push es 878 | 879 | push cx 880 | push di 881 | push ds 882 | push si 883 | 884 | call vmem_driver_entry ; CGA text mode driver - documented later 885 | 886 | ; Increment 32-bit BIOS timer tick counter, once every 18.2 ms 887 | 888 | push cs 889 | pop es 890 | mov bx, timetable 891 | extended_get_rtc 892 | 893 | mov ax, [cs:tm_msec] 894 | sub ax, [cs:last_int8_msec] 895 | 896 | make_ctr_positive: 897 | 898 | cmp ax, 0 899 | jge no_add_1000 900 | 901 | add ax, 1000 902 | jmp make_ctr_positive 903 | 904 | no_add_1000: 905 | 906 | mov bx, 0x40 907 | mov es, bx 908 | 909 | mov dx, 0 910 | mov bx, 1193 911 | mul bx 912 | 913 | mov bx, [es:timer0_freq-bios_data] 914 | 915 | cmp bx, 0 ; 0 actually means FFFF 916 | jne no_adjust_10000 917 | 918 | mov bx, 0xffff 919 | 920 | no_adjust_10000: 921 | 922 | div bx ; AX now contains number of timer ticks since last int 8 (DX is remainder) 923 | 924 | cmp ax, 0 925 | je i8_end 926 | 927 | add word [es:0x6C], ax 928 | adc word [es:0x6E], 0 929 | 930 | inta_call_int8: 931 | 932 | push ax ; Workaround for CPM-86 - INT 1C destroys AX!! 933 | int 8 934 | pop ax 935 | 936 | dec ax 937 | cmp ax, 0 938 | jne inta_call_int8 939 | 940 | mov ax, [cs:tm_msec] 941 | mov [cs:last_int8_msec], ax 942 | 943 | skip_timer_increment: 944 | 945 | ; If last key was from SDL, don't simulate key up events (SDL will do it for us) 946 | cmp byte [cs:last_key_sdl], 0 947 | jne i8_end 948 | 949 | ; See if we have any keys down. If so, release them 950 | cmp byte [es:key_now_down-bios_data], 0 951 | je i8_no_key_down 952 | 953 | mov al, [es:key_now_down-bios_data] 954 | mov byte [es:key_now_down-bios_data], 0 955 | add al, 0x80 956 | call io_key_available 957 | 958 | i8_no_key_down: 959 | 960 | ; See if we have a waiting ESC flag 961 | cmp byte [es:escape_flag-bios_data], 1 962 | jne i8_end 963 | 964 | ; Did we have one last two cycles as well? 965 | cmp byte [es:escape_flag_last-bios_data], 1 966 | je i8_stuff_esc 967 | 968 | inc byte [es:escape_flag_last-bios_data] 969 | jmp i8_end 970 | 971 | i8_stuff_esc: 972 | 973 | ; Yes, clear the ESC flag and put it in the keyboard buffer 974 | mov byte [es:escape_flag-bios_data], 0 975 | mov byte [es:escape_flag_last-bios_data], 0 976 | 977 | ; mov bp, [es:kbbuf_tail-bios_data] 978 | ; mov byte [es:bp], 0x1b ; ESC ASCII code 979 | ; mov byte [es:bp+1], 0x01 ; ESC scan code 980 | 981 | ; ESC keystroke is in the buffer now 982 | ; add word [es:kbbuf_tail-bios_data], 2 983 | ; call kb_adjust_buf ; Wrap the tail around the head if the buffer gets too large 984 | 985 | mov byte [es:this_keystroke-bios_data], 0x1b 986 | 987 | ; Push out ESC keypress/release 988 | mov al, 0x01 989 | call keypress_release 990 | 991 | i8_end: 992 | 993 | ; A Hercules graphics adapter flips bit 7 of I/O port 3BA on refresh 994 | mov dx, 0x3BA 995 | in al, dx 996 | xor al, 0x80 997 | out dx, al 998 | 999 | pop si 1000 | pop ds 1001 | pop di 1002 | pop cx 1003 | 1004 | pop es 1005 | pop bp 1006 | pop dx 1007 | pop bx 1008 | pop ax 1009 | 1010 | iret 1011 | 1012 | ; ************************* INT 8h handler - timer 1013 | 1014 | int8: 1015 | 1016 | int 0x1c 1017 | iret 1018 | 1019 | ; ************************* INT 10h handler - video services 1020 | 1021 | int10: 1022 | 1023 | cmp ah, 0x00 ; Set video mode 1024 | je int10_set_vm 1025 | cmp ah, 0x01 ; Set cursor shape 1026 | je int10_set_cshape 1027 | cmp ah, 0x02 ; Set cursor position 1028 | je int10_set_cursor 1029 | cmp ah, 0x03 ; Get cursur position 1030 | je int10_get_cursor 1031 | cmp ah, 0x06 ; Scroll up window 1032 | je int10_scrollup 1033 | cmp ah, 0x07 ; Scroll down window 1034 | je int10_scrolldown 1035 | cmp ah, 0x08 ; Get character at cursor 1036 | je int10_charatcur 1037 | cmp ah, 0x09 ; Write char and attribute 1038 | je int10_write_char_attrib 1039 | cmp ah, 0x0e ; Write character at cursor position 1040 | je int10_write_char 1041 | cmp ah, 0x0f ; Get video mode 1042 | je int10_get_vm 1043 | ; cmp ah, 0x1a ; Feature check 1044 | ; je int10_features 1045 | 1046 | iret 1047 | 1048 | int10_set_vm: 1049 | 1050 | push dx 1051 | push cx 1052 | push bx 1053 | push es 1054 | 1055 | cmp al, 4 ; CGA mode 4 1056 | je int10_switch_to_cga_gfx 1057 | cmp al, 5 1058 | je int10_switch_to_cga_gfx 1059 | cmp al, 6 1060 | je int10_switch_to_cga_gfx 1061 | 1062 | push ax 1063 | 1064 | mov dx, 0x3b8 1065 | mov al, 0 1066 | out dx, al 1067 | 1068 | mov dx, 0x3b4 1069 | mov al, 1 ; Hercules CRTC "horizontal displayed" register select 1070 | out dx, al 1071 | mov dx, 0x3b5 1072 | mov al, 0x2d ; 0x2D = 45 (* 16) = 720 pixels wide (GRAPHICS_X) 1073 | out dx, al 1074 | mov dx, 0x3b4 1075 | mov al, 6 ; Hercules CRTC "vertical displayed" register select 1076 | out dx, al 1077 | mov dx, 0x3b5 1078 | mov al, 0x57 ; 0x57 = 87 (* 4) = 348 pixels high (GRAPHICS_Y) 1079 | out dx, al 1080 | 1081 | mov dx, 0x40 1082 | mov es, dx 1083 | 1084 | mov byte [es:0xac], 0 ; Tell emulator we are in Hercules mode 1085 | 1086 | pop ax 1087 | 1088 | 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) 1089 | je int10_set_vm_3 1090 | cmp al, 2 ; Same for text mode 2 (mono) 1091 | je int10_set_vm_3 1092 | 1093 | jmp int10_set_vm_continue 1094 | 1095 | int10_switch_to_cga_gfx: 1096 | 1097 | ; Switch to CGA-like graphics mode (with Hercules CRTC set for 640 x 400) 1098 | 1099 | mov dx, 0x40 1100 | mov es, dx 1101 | 1102 | mov [es:0x49], al ; Current video mode 1103 | mov byte [es:0xac], 1 ; Tell emulator we are in CGA mode 1104 | 1105 | mov dx, 0x3b4 1106 | mov al, 1 ; Hercules CRTC "horizontal displayed" register select 1107 | out dx, al 1108 | mov dx, 0x3b5 1109 | mov al, 0x28 ; 0x28 = 40 (* 16) = 640 pixels wide (GRAPHICS_X) 1110 | out dx, al 1111 | mov dx, 0x3b4 1112 | mov al, 6 ; Hercules CRTC "vertical displayed" register select 1113 | out dx, al 1114 | mov dx, 0x3b5 1115 | mov al, 0x64 ; 0x64 = 100 (* 4) = 400 pixels high (GRAPHICS_Y) 1116 | out dx, al 1117 | 1118 | mov dx, 0x3b8 1119 | mov al, 0x8a 1120 | out dx, al 1121 | 1122 | mov bh, 7 1123 | call clear_screen 1124 | 1125 | mov ax, 0x30 1126 | jmp svmn_exit 1127 | 1128 | int10_set_vm_3: 1129 | 1130 | mov al, 3 1131 | 1132 | int10_set_vm_continue: 1133 | 1134 | mov bx, 0x40 1135 | mov es, bx 1136 | 1137 | mov [es:vidmode-bios_data], al 1138 | 1139 | mov bh, 7 ; Black background, white foreground 1140 | call clear_screen ; ANSI clear screen 1141 | 1142 | cmp byte [es:vidmode-bios_data], 6 1143 | je set6 1144 | mov al, 0x30 1145 | jmp svmn 1146 | 1147 | set6: 1148 | 1149 | mov al, 0x3f 1150 | 1151 | svmn: 1152 | 1153 | ; Take Hercules adapter out of graphics mode when resetting video mode via int 10 1154 | push ax 1155 | mov dx, 0x3B8 1156 | mov al, 0 1157 | out dx, al 1158 | pop ax 1159 | 1160 | svmn_exit: 1161 | 1162 | pop es 1163 | pop bx 1164 | pop cx 1165 | pop dx 1166 | iret 1167 | 1168 | int10_set_cshape: 1169 | 1170 | push ds 1171 | push ax 1172 | push cx 1173 | 1174 | mov ax, 0x40 1175 | mov ds, ax 1176 | 1177 | mov byte [cursor_visible-bios_data], 1 ; Show cursor 1178 | 1179 | and ch, 01100000b 1180 | cmp ch, 00100000b 1181 | jne cur_visible 1182 | 1183 | mov byte [cursor_visible-bios_data], 0 ; Hide cursor 1184 | call ansi_hide_cursor 1185 | jmp cur_done 1186 | 1187 | cur_visible: 1188 | 1189 | call ansi_show_cursor 1190 | 1191 | cur_done: 1192 | 1193 | pop cx 1194 | pop ax 1195 | pop ds 1196 | iret 1197 | 1198 | int10_set_cursor: 1199 | 1200 | push ds 1201 | push ax 1202 | 1203 | mov ax, 0x40 1204 | mov ds, ax 1205 | 1206 | mov [curpos_y-bios_data], dh 1207 | mov [crt_curpos_y-bios_data], dh 1208 | mov [curpos_x-bios_data], dl 1209 | mov [crt_curpos_x-bios_data], dl 1210 | 1211 | cmp dh, 24 1212 | jbe skip_set_cur_row_max 1213 | 1214 | ; If cursor is moved off the screen, then hide it 1215 | call ansi_hide_cursor 1216 | jmp skip_set_cur_ansi 1217 | 1218 | skip_set_cur_row_max: 1219 | 1220 | cmp dl, 79 1221 | jbe skip_set_cur_col_max 1222 | 1223 | ; If cursor is moved off the screen, then hide it 1224 | call ansi_hide_cursor 1225 | jmp skip_set_cur_ansi 1226 | 1227 | skip_set_cur_col_max: 1228 | 1229 | mov al, 0x1B ; ANSI 1230 | extended_putchar_al 1231 | mov al, '[' ; ANSI 1232 | extended_putchar_al 1233 | mov al, dh ; Row number 1234 | inc al 1235 | call puts_decimal_al 1236 | mov al, ';' ; ANSI 1237 | extended_putchar_al 1238 | mov al, dl ; Column number 1239 | inc al 1240 | call puts_decimal_al 1241 | mov al, 'H' ; Set cursor position command 1242 | extended_putchar_al 1243 | 1244 | cmp byte [cursor_visible-bios_data], 1 1245 | jne skip_set_cur_ansi 1246 | call ansi_show_cursor 1247 | 1248 | skip_set_cur_ansi: 1249 | 1250 | pop ax 1251 | pop ds 1252 | iret 1253 | 1254 | int10_get_cursor: 1255 | 1256 | push es 1257 | 1258 | mov cx, 0x40 1259 | mov es, cx 1260 | 1261 | mov cx, 0x0607 1262 | mov dl, [es:curpos_x-bios_data] 1263 | mov dh, [es:curpos_y-bios_data] 1264 | 1265 | pop es 1266 | 1267 | iret 1268 | 1269 | int10_scrollup: 1270 | 1271 | push bx 1272 | push cx 1273 | push bp 1274 | push ax 1275 | 1276 | mov bp, bx ; Convert from CGA to ANSI 1277 | mov cl, 12 1278 | ror bp, cl 1279 | and bp, 7 1280 | mov bl, byte [cs:bp+colour_table] 1281 | add bl, 10 1282 | 1283 | mov al, 0x1B ; Escape 1284 | extended_putchar_al 1285 | mov al, '[' ; ANSI 1286 | extended_putchar_al 1287 | mov al, bl ; Background colour 1288 | call puts_decimal_al 1289 | mov al, 'm' ; Set cursor position command 1290 | extended_putchar_al 1291 | 1292 | pop ax 1293 | pop bp 1294 | pop cx 1295 | pop bx 1296 | 1297 | cmp al, 0 ; Clear window 1298 | jne cls_partial 1299 | 1300 | cmp cx, 0 ; Start of screen 1301 | jne cls_partial 1302 | 1303 | cmp dl, 0x4f ; Clearing columns 0-79 1304 | jb cls_partial 1305 | 1306 | cmp dh, 0x18 ; Clearing rows 0-24 (or more) 1307 | jb cls_partial 1308 | 1309 | call clear_screen 1310 | iret 1311 | 1312 | cls_partial: 1313 | 1314 | push ax 1315 | push bx 1316 | 1317 | mov bl, al ; Number of rows to scroll are now in bl 1318 | cmp bl, 0 ; Clear whole window? 1319 | jne cls_partial_up_whole 1320 | 1321 | mov bl, 25 ; 25 rows 1322 | 1323 | cls_partial_up_whole: 1324 | 1325 | mov al, 0x1B ; Escape 1326 | extended_putchar_al 1327 | mov al, '[' ; ANSI 1328 | extended_putchar_al 1329 | 1330 | cmp ch, 0 ; Start row 1? Maybe full screen 1331 | je cls_maybe_fs 1332 | jmp cls_not_fs 1333 | 1334 | cls_maybe_fs: 1335 | 1336 | cmp dh, 24 ; End row 25? Full screen for sure 1337 | je cls_fs 1338 | 1339 | cls_not_fs: 1340 | 1341 | mov al, ch ; Start row 1342 | inc al 1343 | call puts_decimal_al 1344 | mov al, ';' ; ANSI 1345 | extended_putchar_al 1346 | mov al, dh ; End row 1347 | inc al 1348 | call puts_decimal_al 1349 | 1350 | cls_fs: 1351 | 1352 | mov al, 'r' ; Set scrolling window 1353 | extended_putchar_al 1354 | 1355 | mov al, 0x1B ; Escape 1356 | extended_putchar_al 1357 | mov al, '[' ; ANSI 1358 | extended_putchar_al 1359 | 1360 | cmp bl, 1 1361 | jne cls_fs_multiline 1362 | 1363 | mov al, 'M' 1364 | jmp cs_fs_ml_out 1365 | 1366 | cls_fs_multiline: 1367 | 1368 | mov al, bl ; Number of rows 1369 | call puts_decimal_al 1370 | mov al, 'S' ; Scroll up 1371 | 1372 | cs_fs_ml_out: 1373 | 1374 | extended_putchar_al 1375 | 1376 | pop bx 1377 | pop ax 1378 | 1379 | ; Update "actual" cursor position with expected value - different ANSI terminals do different things 1380 | ; to the cursor position when you scroll 1381 | 1382 | push ax 1383 | push bx 1384 | push dx 1385 | push es 1386 | 1387 | mov ax, 0x40 1388 | mov es, ax 1389 | 1390 | mov ah, 2 1391 | mov bh, 0 1392 | mov dh, [es:curpos_y-bios_data] 1393 | mov dl, [es:curpos_x-bios_data] 1394 | int 10h 1395 | 1396 | pop es 1397 | pop dx 1398 | pop bx 1399 | pop ax 1400 | 1401 | int10_scroll_up_vmem_update: 1402 | 1403 | ; Now, we need to update video memory 1404 | 1405 | push bx 1406 | push ax 1407 | 1408 | push ds 1409 | push es 1410 | push cx 1411 | push dx 1412 | push si 1413 | push di 1414 | 1415 | mov byte [cs:vram_dirty], 1 1416 | 1417 | push bx 1418 | 1419 | mov bx, 0xb800 1420 | mov es, bx 1421 | mov ds, bx 1422 | 1423 | pop bx 1424 | mov bl, al 1425 | 1426 | cls_vmem_scroll_up_next_line: 1427 | 1428 | cmp bl, 0 1429 | je cls_vmem_scroll_up_done 1430 | 1431 | cls_vmem_scroll_up_one: 1432 | 1433 | push bx 1434 | push dx 1435 | 1436 | mov ax, 0 1437 | mov al, ch ; Start row number is now in AX 1438 | mov bx, 80 1439 | mul bx 1440 | add al, cl 1441 | adc ah, 0 ; Character number is now in AX 1442 | mov bx, 2 1443 | mul bx ; Memory location is now in AX 1444 | 1445 | pop dx 1446 | pop bx 1447 | 1448 | mov di, ax 1449 | mov si, ax 1450 | add si, 2*80 ; In a moment we will copy CX words from DS:SI to ES:DI 1451 | 1452 | mov ax, 0 1453 | add al, dl 1454 | adc ah, 0 1455 | inc ax 1456 | sub al, cl 1457 | sbb ah, 0 ; AX now contains the number of characters from the row to copy 1458 | 1459 | cmp ch, dh 1460 | jae cls_vmem_scroll_up_one_done 1461 | 1462 | vmem_scroll_up_copy_next_row: 1463 | 1464 | push cx 1465 | mov cx, ax ; CX is now the length (in words) of the row to copy 1466 | cld 1467 | rep movsw ; Scroll the line up 1468 | pop cx 1469 | 1470 | inc ch ; Move onto the next row 1471 | jmp cls_vmem_scroll_up_one 1472 | 1473 | cls_vmem_scroll_up_one_done: 1474 | 1475 | push cx 1476 | mov cx, ax ; CX is now the length (in words) of the row to copy 1477 | mov ah, bh ; Attribute for new line 1478 | mov al, 0 ; Write 0 to video memory for new characters 1479 | cld 1480 | rep stosw 1481 | pop cx 1482 | 1483 | dec bl ; Scroll whole text block another line 1484 | jmp cls_vmem_scroll_up_next_line 1485 | 1486 | cls_vmem_scroll_up_done: 1487 | 1488 | ;mov al, 0x1B ; Escape 1489 | ;extended_putchar_al 1490 | ;mov al, '[' ; ANSI 1491 | ;extended_putchar_al 1492 | ;mov al, '0' ; Reset attributes 1493 | ;extended_putchar_al 1494 | ;mov al, 'm' 1495 | ;extended_putchar_al 1496 | 1497 | pop di 1498 | pop si 1499 | pop dx 1500 | pop cx 1501 | pop es 1502 | pop ds 1503 | 1504 | pop ax 1505 | pop bx 1506 | 1507 | iret 1508 | 1509 | int10_scrolldown: 1510 | 1511 | push bx 1512 | push cx 1513 | push bp 1514 | push ax 1515 | 1516 | mov bp, bx ; Convert from CGA to ANSI 1517 | mov cl, 12 1518 | ror bp, cl 1519 | and bp, 7 1520 | mov bl, byte [cs:bp+colour_table] 1521 | add bl, 10 1522 | 1523 | mov al, 0x1B ; Escape 1524 | extended_putchar_al 1525 | mov al, '[' ; ANSI 1526 | extended_putchar_al 1527 | mov al, bl ; Background colour 1528 | call puts_decimal_al 1529 | mov al, 'm' ; Set cursor position command 1530 | extended_putchar_al 1531 | 1532 | pop ax 1533 | pop bp 1534 | pop cx 1535 | pop bx 1536 | 1537 | cmp al, 0 ; Clear window 1538 | jne cls_partial_down 1539 | 1540 | cmp cx, 0 ; Start of screen 1541 | jne cls_partial_down 1542 | 1543 | cmp dl, 0x4f ; Clearing columns 0-79 1544 | jne cls_partial_down 1545 | 1546 | cmp dh, 0x18 ; Clearing rows 0-24 (or more) 1547 | jl cls_partial_down 1548 | 1549 | call clear_screen 1550 | iret 1551 | 1552 | cls_partial_down: 1553 | 1554 | push ax 1555 | push bx 1556 | 1557 | mov bx, 0 1558 | mov bl, al ; Number of rows to scroll are now in bl 1559 | 1560 | cmp bl, 0 ; Clear whole window? 1561 | jne cls_partial_down_whole 1562 | 1563 | mov bl, 25 ; 25 rows 1564 | 1565 | cls_partial_down_whole: 1566 | 1567 | mov al, 0x1B ; Escape 1568 | extended_putchar_al 1569 | mov al, '[' ; ANSI 1570 | extended_putchar_al 1571 | 1572 | cmp ch, 0 ; Start row 1? Maybe full screen 1573 | je cls_maybe_fs_down 1574 | jmp cls_not_fs_down 1575 | 1576 | cls_maybe_fs_down: 1577 | 1578 | cmp dh, 24 ; End row 25? Full screen for sure 1579 | je cls_fs_down 1580 | 1581 | cls_not_fs_down: 1582 | 1583 | mov al, ch ; Start row 1584 | inc al 1585 | call puts_decimal_al 1586 | mov al, ';' ; ANSI 1587 | extended_putchar_al 1588 | mov al, dh ; End row 1589 | inc al 1590 | call puts_decimal_al 1591 | 1592 | cls_fs_down: 1593 | 1594 | mov al, 'r' ; Set scrolling window 1595 | extended_putchar_al 1596 | 1597 | mov al, 0x1B ; Escape 1598 | extended_putchar_al 1599 | mov al, '[' ; ANSI 1600 | extended_putchar_al 1601 | 1602 | cmp bl, 1 1603 | jne cls_fs_down_multiline 1604 | 1605 | mov al, 'D' 1606 | jmp cs_fs_down_ml_out 1607 | 1608 | cls_fs_down_multiline: 1609 | 1610 | mov al, bl ; Number of rows 1611 | call puts_decimal_al 1612 | mov al, 'T' ; Scroll down 1613 | 1614 | cs_fs_down_ml_out: 1615 | 1616 | extended_putchar_al 1617 | 1618 | ; Update "actual" cursor position with expected value - different ANSI terminals do different things 1619 | ; to the cursor position when you scroll 1620 | 1621 | pop bx 1622 | pop ax 1623 | 1624 | push ax 1625 | push bx 1626 | push dx 1627 | push es 1628 | 1629 | mov ax, 0x40 1630 | mov es, ax 1631 | 1632 | mov ah, 2 1633 | mov bh, 0 1634 | mov dh, [es:curpos_y-bios_data] 1635 | mov dl, [es:curpos_x-bios_data] 1636 | int 10h 1637 | 1638 | pop es 1639 | pop dx 1640 | pop bx 1641 | pop ax 1642 | 1643 | int10_scroll_down_vmem_update: 1644 | 1645 | ; Now, we need to update video memory 1646 | 1647 | push ax 1648 | push bx 1649 | 1650 | push ds 1651 | push es 1652 | push cx 1653 | push dx 1654 | push si 1655 | push di 1656 | 1657 | mov byte [cs:vram_dirty], 1 1658 | 1659 | push bx 1660 | 1661 | mov bx, 0xb800 1662 | mov es, bx 1663 | mov ds, bx 1664 | 1665 | pop bx 1666 | mov bl, al 1667 | 1668 | cls_vmem_scroll_down_next_line: 1669 | 1670 | cmp bl, 0 1671 | je cls_vmem_scroll_down_done 1672 | 1673 | cls_vmem_scroll_down_one: 1674 | 1675 | push bx 1676 | push dx 1677 | 1678 | mov ax, 0 1679 | mov al, dh ; End row number is now in AX 1680 | mov bx, 80 1681 | mul bx 1682 | add al, cl 1683 | adc ah, 0 ; Character number is now in AX 1684 | mov bx, 2 1685 | mul bx ; Memory location (start of final row) is now in AX 1686 | 1687 | pop dx 1688 | pop bx 1689 | 1690 | mov di, ax 1691 | mov si, ax 1692 | sub si, 2*80 ; In a moment we will copy CX words from DS:SI to ES:DI 1693 | 1694 | mov ax, 0 1695 | add al, dl 1696 | adc ah, 0 1697 | inc ax 1698 | sub al, cl 1699 | sbb ah, 0 ; AX now contains the number of characters from the row to copy 1700 | 1701 | cmp ch, dh 1702 | jae cls_vmem_scroll_down_one_done 1703 | 1704 | push cx 1705 | mov cx, ax ; CX is now the length (in words) of the row to copy 1706 | rep movsw ; Scroll the line down 1707 | pop cx 1708 | 1709 | dec dh ; Move onto the next row 1710 | jmp cls_vmem_scroll_down_one 1711 | 1712 | cls_vmem_scroll_down_one_done: 1713 | 1714 | push cx 1715 | mov cx, ax ; CX is now the length (in words) of the row to copy 1716 | mov ah, bh ; Attribute for new line 1717 | mov al, 0 ; Write 0 to video memory for new characters 1718 | rep stosw 1719 | pop cx 1720 | 1721 | dec bl ; Scroll whole text block another line 1722 | jmp cls_vmem_scroll_down_next_line 1723 | 1724 | cls_vmem_scroll_down_done: 1725 | 1726 | pop di 1727 | pop si 1728 | pop dx 1729 | pop cx 1730 | pop es 1731 | pop ds 1732 | 1733 | ;mov al, 0x1B ; Escape 1734 | ;extended_putchar_al 1735 | ;mov al, '[' ; ANSI 1736 | ;extended_putchar_al 1737 | ;mov al, '0' ; Reset attributes 1738 | ;extended_putchar_al 1739 | ;mov al, 'm' 1740 | ;extended_putchar_al 1741 | 1742 | pop bx 1743 | pop ax 1744 | iret 1745 | 1746 | int10_charatcur: 1747 | 1748 | ; This returns the character at the cursor. It is completely dysfunctional, 1749 | ; and only works at all if the character has previously been written following 1750 | ; an int 10/ah = 2 call to set the cursor position. Added just to support 1751 | ; GWBASIC. 1752 | 1753 | push ds 1754 | push es 1755 | push bx 1756 | push dx 1757 | 1758 | mov bx, 0x40 1759 | mov es, bx 1760 | 1761 | mov bx, 0xc000 1762 | mov ds, bx 1763 | 1764 | mov bx, 160 1765 | mov ax, 0 1766 | mov al, [es:curpos_y-bios_data] 1767 | mul bx 1768 | 1769 | mov bx, 0 1770 | mov bl, [es:curpos_x-bios_data] 1771 | add ax, bx 1772 | add ax, bx 1773 | mov bx, ax 1774 | 1775 | mov ah, 7 1776 | mov al, [bx] 1777 | 1778 | pop dx 1779 | pop bx 1780 | pop es 1781 | pop ds 1782 | 1783 | iret 1784 | 1785 | i10_unsup: 1786 | 1787 | iret 1788 | 1789 | int10_write_char: 1790 | 1791 | ; First write the character to a buffer at C000:0. This is so that 1792 | ; we can later retrieve it using the get character at cursor function, 1793 | ; which GWBASIC uses. 1794 | 1795 | push ds 1796 | push es 1797 | push cx 1798 | push dx 1799 | push ax 1800 | push bp 1801 | push bx 1802 | 1803 | push ax 1804 | 1805 | mov cl, al 1806 | mov ch, 7 1807 | 1808 | mov bx, 0x40 1809 | mov es, bx 1810 | 1811 | mov bx, 0xc000 1812 | mov ds, bx 1813 | 1814 | mov bx, 160 1815 | mov ax, 0 1816 | mov al, [es:curpos_y-bios_data] 1817 | mul bx 1818 | 1819 | mov bx, 0 1820 | mov bl, [es:curpos_x-bios_data] 1821 | shl bx, 1 1822 | add bx, ax 1823 | 1824 | mov [bx], cx 1825 | 1826 | pop ax 1827 | push ax 1828 | 1829 | extended_putchar_al 1830 | 1831 | jmp int10_write_char_skip_lines 1832 | 1833 | int10_write_char_attrib: 1834 | 1835 | ; First write the character to a buffer at C000:0. This is so that 1836 | ; we can later retrieve it using the get character at cursor function, 1837 | ; which GWBASIC uses. 1838 | 1839 | push ds 1840 | push es 1841 | push cx 1842 | push dx 1843 | push ax 1844 | push bp 1845 | push bx 1846 | 1847 | push ax 1848 | push cx 1849 | 1850 | mov cl, al 1851 | mov ch, bl 1852 | 1853 | mov bx, 0x40 1854 | mov es, bx 1855 | 1856 | mov bx, 0xc000 1857 | mov ds, bx 1858 | 1859 | mov bx, 160 1860 | mov ax, 0 1861 | mov al, [es:curpos_y-bios_data] 1862 | mul bx 1863 | 1864 | mov bx, 0 1865 | mov bl, [es:curpos_x-bios_data] 1866 | shl bx, 1 1867 | add bx, ax 1868 | 1869 | mov [bx], cx 1870 | 1871 | mov bl, ch 1872 | 1873 | mov bh, bl 1874 | and bl, 7 ; Foreground colour now in bl 1875 | 1876 | mov bp, bx ; Convert from CGA to ANSI 1877 | and bp, 0xff 1878 | mov bl, byte [cs:bp+colour_table] 1879 | 1880 | and bh, 8 ; Bright attribute now in bh 1881 | cpu 186 1882 | shr bh, 3 1883 | cpu 8086 1884 | 1885 | mov al, 0x1B ; Escape 1886 | extended_putchar_al 1887 | mov al, '[' ; ANSI 1888 | extended_putchar_al 1889 | mov al, bh ; Bright attribute 1890 | call puts_decimal_al 1891 | mov al, ';' 1892 | extended_putchar_al 1893 | mov al, bl ; Foreground colour 1894 | call puts_decimal_al 1895 | 1896 | mov bl, ch 1897 | 1898 | mov bh, bl 1899 | cpu 186 1900 | shr bl, 4 1901 | cpu 8086 1902 | and bl, 7 ; Background colour now in bl 1903 | 1904 | mov bp, bx ; Convert from CGA to ANSI 1905 | and bp, 0xff 1906 | mov bl, byte [cs:bp+colour_table] 1907 | 1908 | add bl, 10 1909 | ; rol bh, 1 1910 | ; and bh, 1 ; Bright attribute now in bh (not used right now) 1911 | 1912 | mov al, ';' 1913 | extended_putchar_al 1914 | mov al, bl ; Background colour 1915 | call puts_decimal_al 1916 | mov al, 'm' ; Set cursor position command 1917 | extended_putchar_al 1918 | 1919 | pop cx 1920 | pop ax 1921 | push ax 1922 | 1923 | out_another_char: 1924 | 1925 | extended_putchar_al 1926 | dec cx 1927 | cmp cx, 0 1928 | jne out_another_char 1929 | 1930 | mov al, 0x1B ; Escape 1931 | extended_putchar_al 1932 | mov al, '[' ; ANSI 1933 | extended_putchar_al 1934 | mov al, '0' ; Reset attributes 1935 | extended_putchar_al 1936 | mov al, 'm' 1937 | extended_putchar_al 1938 | 1939 | int10_write_char_skip_lines: 1940 | 1941 | pop ax 1942 | 1943 | push es 1944 | pop ds 1945 | 1946 | cmp al, 0x08 1947 | jne int10_write_char_attrib_inc_x 1948 | 1949 | dec byte [curpos_x-bios_data] 1950 | dec byte [crt_curpos_x-bios_data] 1951 | cmp byte [curpos_x-bios_data], 0 1952 | jg int10_write_char_attrib_done 1953 | 1954 | mov byte [curpos_x-bios_data], 0 1955 | mov byte [crt_curpos_x-bios_data], 0 1956 | jmp int10_write_char_attrib_done 1957 | 1958 | int10_write_char_attrib_inc_x: 1959 | 1960 | cmp al, 0x0A ; New line? 1961 | je int10_write_char_attrib_newline 1962 | 1963 | cmp al, 0x0D ; Carriage return? 1964 | jne int10_write_char_attrib_not_cr 1965 | 1966 | mov byte [curpos_x-bios_data], 0 1967 | mov byte [crt_curpos_x-bios_data], 0 1968 | jmp int10_write_char_attrib_done 1969 | 1970 | int10_write_char_attrib_not_cr: 1971 | 1972 | inc byte [curpos_x-bios_data] 1973 | inc byte [crt_curpos_x-bios_data] 1974 | cmp byte [curpos_x-bios_data], 80 1975 | jge int10_write_char_attrib_newline 1976 | jmp int10_write_char_attrib_done 1977 | 1978 | int10_write_char_attrib_newline: 1979 | 1980 | mov byte [curpos_x-bios_data], 0 1981 | mov byte [crt_curpos_x-bios_data], 0 1982 | inc byte [curpos_y-bios_data] 1983 | inc byte [crt_curpos_y-bios_data] 1984 | 1985 | cmp byte [curpos_y-bios_data], 25 1986 | jb int10_write_char_attrib_done 1987 | mov byte [curpos_y-bios_data], 24 1988 | mov byte [crt_curpos_y-bios_data], 24 1989 | 1990 | mov bh, 7 1991 | mov al, 1 1992 | mov cx, 0 1993 | mov dx, 0x184f 1994 | 1995 | pushf 1996 | push cs 1997 | call int10_scroll_up_vmem_update 1998 | 1999 | int10_write_char_attrib_done: 2000 | 2001 | pop bx 2002 | pop bp 2003 | pop ax 2004 | pop dx 2005 | pop cx 2006 | pop es 2007 | pop ds 2008 | 2009 | iret 2010 | 2011 | int10_get_vm: 2012 | 2013 | push es 2014 | 2015 | mov ax, 0x40 2016 | mov es, ax 2017 | 2018 | mov ah, 80 ; Number of columns 2019 | mov al, [es:vidmode-bios_data] 2020 | mov bh, 0 2021 | 2022 | pop es 2023 | 2024 | iret 2025 | 2026 | int10_features: 2027 | 2028 | ; Signify we have CGA display 2029 | 2030 | ; mov al, 0x1a 2031 | ; mov bx, 0x0202 2032 | ; iret 2033 | 2034 | ; ************************* INT 11h - get equipment list 2035 | 2036 | int11: 2037 | mov ax, [cs:equip] 2038 | iret 2039 | 2040 | ; ************************* INT 12h - return memory size 2041 | 2042 | int12: 2043 | mov ax, 0x280 ; 640K conventional memory 2044 | iret 2045 | 2046 | ; ************************* INT 13h handler - disk services 2047 | 2048 | int13: 2049 | cmp ah, 0x00 ; Reset disk 2050 | je int13_reset_disk 2051 | cmp ah, 0x01 ; Get last status 2052 | je int13_last_status 2053 | 2054 | cmp dl, 0x80 ; Hard disk being queried? 2055 | jne i13_diskok 2056 | 2057 | ; Now, need to check an HD is installed 2058 | cmp word [cs:num_disks], 2 2059 | jge i13_diskok 2060 | 2061 | ; No HD, so return an error 2062 | mov ah, 15 ; Report no such drive 2063 | jmp reach_stack_stc 2064 | 2065 | i13_diskok: 2066 | 2067 | cmp ah, 0x02 ; Read disk 2068 | je int13_read_disk 2069 | cmp ah, 0x03 ; Write disk 2070 | je int13_write_disk 2071 | cmp ah, 0x04 ; Verify disk 2072 | je int13_verify 2073 | cmp ah, 0x05 ; Format track - does nothing here 2074 | je int13_format 2075 | cmp ah, 0x08 ; Get drive parameters (hard disk) 2076 | je int13_getparams 2077 | cmp ah, 0x0c ; Seek (hard disk) 2078 | je int13_seek 2079 | cmp ah, 0x10 ; Check if drive ready (hard disk) 2080 | je int13_hdready 2081 | cmp ah, 0x15 ; Get disk type 2082 | je int13_getdisktype 2083 | cmp ah, 0x16 ; Detect disk change 2084 | je int13_diskchange 2085 | 2086 | mov ah, 1 ; Invalid function 2087 | jmp reach_stack_stc 2088 | 2089 | iret 2090 | 2091 | int13_reset_disk: 2092 | 2093 | jmp reach_stack_clc 2094 | 2095 | int13_last_status: 2096 | 2097 | mov ah, [cs:disk_laststatus] 2098 | je ls_no_error 2099 | 2100 | stc 2101 | iret 2102 | 2103 | ls_no_error: 2104 | 2105 | clc 2106 | iret 2107 | 2108 | int13_read_disk: 2109 | 2110 | push dx 2111 | 2112 | cmp dl, 0 ; Floppy 0 2113 | je i_flop_rd 2114 | cmp dl, 0x80 ; HD 2115 | je i_hd_rd 2116 | 2117 | pop dx 2118 | mov ah, 1 2119 | jmp reach_stack_stc 2120 | 2121 | i_flop_rd: 2122 | 2123 | push si 2124 | push bp 2125 | 2126 | cmp cl, [cs:int1e_spt] 2127 | ja rd_error 2128 | 2129 | pop bp 2130 | pop si 2131 | 2132 | mov dl, 1 ; Floppy disk file handle is stored at j[1] in emulator 2133 | jmp i_rd 2134 | 2135 | i_hd_rd: 2136 | 2137 | mov dl, 0 ; Hard disk file handle is stored at j[0] in emulator 2138 | 2139 | i_rd: 2140 | 2141 | push si 2142 | push bp 2143 | 2144 | ; Convert head/cylinder/sector number to byte offset in disk image 2145 | 2146 | call chs_to_abs 2147 | 2148 | ; Now, SI:BP contains the absolute sector offset of the block. We then multiply by 512 to get the offset into the disk image 2149 | 2150 | mov ah, 0 2151 | cpu 186 2152 | shl ax, 9 2153 | extended_read_disk 2154 | shr ax, 9 2155 | cpu 8086 2156 | mov ah, 0x02 ; Put read code back 2157 | 2158 | cmp al, 0 2159 | je rd_error 2160 | 2161 | ; Read was successful. Now, check if we have read the boot sector. If so, we want to update 2162 | ; our internal table of sectors/track to match the disk format 2163 | 2164 | cmp dx, 1 ; FDD? 2165 | jne rd_noerror 2166 | cmp cx, 1 ; First sector? 2167 | jne rd_noerror 2168 | 2169 | push ax 2170 | 2171 | mov al, [es:bx+24] ; Number of SPT in floppy disk BPB 2172 | 2173 | ; cmp al, 0 ; If disk is unformatted, do not update the table 2174 | ; jne rd_update_spt 2175 | cmp al, 9 ; 9 SPT, i.e. 720K disk, so update the table 2176 | je rd_update_spt 2177 | cmp al, 18 2178 | je rd_update_spt ; 18 SPT, i.e. 1.44MB disk, so update the table 2179 | 2180 | pop ax 2181 | 2182 | jmp rd_noerror 2183 | 2184 | rd_update_spt: 2185 | 2186 | mov [cs:int1e_spt], al 2187 | pop ax 2188 | 2189 | rd_noerror: 2190 | 2191 | clc 2192 | mov ah, 0 ; No error 2193 | jmp rd_finish 2194 | 2195 | rd_error: 2196 | 2197 | stc 2198 | mov ah, 4 ; Sector not found 2199 | 2200 | rd_finish: 2201 | 2202 | pop bp 2203 | pop si 2204 | pop dx 2205 | 2206 | mov [cs:disk_laststatus], ah 2207 | jmp reach_stack_carry 2208 | 2209 | int13_write_disk: 2210 | 2211 | push dx 2212 | 2213 | cmp dl, 0 ; Floppy 0 2214 | je i_flop_wr 2215 | cmp dl, 0x80 ; HD 2216 | je i_hd_wr 2217 | 2218 | pop dx 2219 | mov ah, 1 2220 | jmp reach_stack_stc 2221 | 2222 | i_flop_wr: 2223 | 2224 | mov dl, 1 ; Floppy disk file handle is stored at j[1] in emulator 2225 | jmp i_wr 2226 | 2227 | i_hd_wr: 2228 | 2229 | mov dl, 0 ; Hard disk file handle is stored at j[0] in emulator 2230 | 2231 | i_wr: 2232 | 2233 | push si 2234 | push bp 2235 | push cx 2236 | push di 2237 | 2238 | ; Convert head/cylinder/sector number to byte offset in disk image 2239 | 2240 | call chs_to_abs 2241 | 2242 | ; Signal an error if we are trying to write beyond the end of the disk 2243 | 2244 | cmp dl, 0 ; Hard disk? 2245 | jne wr_fine ; No - no need for disk sector valid check - NOTE: original submission was JNAE which caused write problems on floppy disk 2246 | 2247 | ; First, we add the number of sectors we are trying to write from the absolute 2248 | ; sector number returned by chs_to_abs. We need to have at least this many 2249 | ; sectors on the disk, otherwise return a sector not found error. 2250 | 2251 | mov cx, bp 2252 | mov di, si 2253 | 2254 | mov ah, 0 2255 | add cx, ax 2256 | adc di, 0 2257 | 2258 | cmp di, [cs:hd_secs_hi] 2259 | ja wr_error 2260 | jb wr_fine 2261 | cmp cx, [cs:hd_secs_lo] 2262 | ja wr_error 2263 | 2264 | wr_fine: 2265 | 2266 | mov ah, 0 2267 | cpu 186 2268 | shl ax, 9 2269 | extended_write_disk 2270 | shr ax, 9 2271 | cpu 8086 2272 | mov ah, 0x03 ; Put write code back 2273 | 2274 | cmp al, 0 2275 | je wr_error 2276 | 2277 | clc 2278 | mov ah, 0 ; No error 2279 | jmp wr_finish 2280 | 2281 | wr_error: 2282 | 2283 | stc 2284 | mov ah, 4 ; Sector not found 2285 | 2286 | wr_finish: 2287 | 2288 | pop di 2289 | pop cx 2290 | pop bp 2291 | pop si 2292 | pop dx 2293 | 2294 | mov [cs:disk_laststatus], ah 2295 | jmp reach_stack_carry 2296 | 2297 | int13_verify: 2298 | 2299 | mov ah, 0 2300 | jmp reach_stack_clc 2301 | 2302 | int13_getparams: 2303 | 2304 | cmp dl, 0 2305 | je i_gp_fl 2306 | cmp dl, 0x80 2307 | je i_gp_hd 2308 | 2309 | mov ah, 0x01 2310 | mov [cs:disk_laststatus], ah 2311 | jmp reach_stack_stc 2312 | 2313 | i_gp_fl: 2314 | 2315 | push cs 2316 | pop es 2317 | mov di, int1e ; ES:DI now points to floppy parameters table (INT 1E) 2318 | 2319 | mov ax, 0 2320 | mov bx, 4 2321 | mov ch, 0x4f 2322 | mov cl, [cs:int1e_spt] 2323 | mov dx, 0x0101 2324 | 2325 | mov byte [cs:disk_laststatus], 0 2326 | jmp reach_stack_clc 2327 | 2328 | i_gp_hd: 2329 | 2330 | mov ax, 0 2331 | mov bx, 0 2332 | mov dl, 1 2333 | mov dh, [cs:hd_max_head] 2334 | mov cx, [cs:hd_max_track] 2335 | ror ch, 1 2336 | ror ch, 1 2337 | add ch, [cs:hd_max_sector] 2338 | xchg ch, cl 2339 | 2340 | mov byte [cs:disk_laststatus], 0 2341 | jmp reach_stack_clc 2342 | 2343 | int13_seek: 2344 | 2345 | mov ah, 0 2346 | jmp reach_stack_clc 2347 | 2348 | int13_hdready: 2349 | 2350 | cmp byte [cs:num_disks], 2 ; HD present? 2351 | jne int13_hdready_nohd 2352 | cmp dl, 0x80 ; Checking first HD? 2353 | jne int13_hdready_nohd 2354 | 2355 | mov ah, 0 2356 | jmp reach_stack_clc 2357 | 2358 | int13_hdready_nohd: 2359 | 2360 | jmp reach_stack_stc 2361 | 2362 | int13_format: 2363 | 2364 | mov ah, 0 2365 | jmp reach_stack_clc 2366 | 2367 | int13_getdisktype: 2368 | 2369 | cmp dl, 0 ; Floppy 2370 | je gdt_flop 2371 | cmp dl, 0x80 ; HD 2372 | je gdt_hd 2373 | 2374 | mov ah, 15 ; Report no such drive 2375 | mov [cs:disk_laststatus], ah 2376 | jmp reach_stack_stc 2377 | 2378 | gdt_flop: 2379 | 2380 | mov ah, 1 2381 | jmp reach_stack_clc 2382 | 2383 | gdt_hd: 2384 | 2385 | mov ah, 3 2386 | mov cx, [cs:hd_secs_hi] 2387 | mov dx, [cs:hd_secs_lo] 2388 | jmp reach_stack_clc 2389 | 2390 | int13_diskchange: 2391 | 2392 | mov ah, 0 ; Disk not changed 2393 | jmp reach_stack_clc 2394 | 2395 | ; ************************* INT 14h - serial port functions 2396 | 2397 | int14: 2398 | cmp ah, 0 2399 | je int14_init 2400 | 2401 | jmp reach_stack_stc 2402 | 2403 | int14_init: 2404 | 2405 | mov ax, 0 2406 | jmp reach_stack_stc 2407 | 2408 | ; ************************* INT 15h - get system configuration 2409 | 2410 | int15: ; Here we do not support any of the functions, and just return 2411 | ; a function not supported code - like the original IBM PC/XT does. 2412 | 2413 | ; cmp ah, 0xc0 2414 | ; je int15_sysconfig 2415 | ; cmp ah, 0x41 2416 | ; je int15_waitevent 2417 | ; cmp ah, 0x4f 2418 | ; je int15_intercept 2419 | ; cmp ah, 0x88 2420 | ; je int15_getextmem 2421 | 2422 | ; Otherwise, function not supported 2423 | 2424 | mov ah, 0x86 2425 | 2426 | jmp reach_stack_stc 2427 | 2428 | ; int15_sysconfig: ; Return address of system configuration table in ROM 2429 | ; 2430 | ; mov bx, 0xf000 2431 | ; mov es, bx 2432 | ; mov bx, rom_config 2433 | ; mov ah, 0 2434 | ; 2435 | ; jmp reach_stack_clc 2436 | ; 2437 | ; int15_waitevent: ; Events not supported 2438 | ; 2439 | ; mov ah, 0x86 2440 | ; 2441 | ; jmp reach_stack_stc 2442 | ; 2443 | ; int15_intercept: ; Keyboard intercept 2444 | ; 2445 | ; jmp reach_stack_stc 2446 | ; 2447 | ; int15_getextmem: ; Extended memory not supported 2448 | ; 2449 | ; mov ah,0x86 2450 | ; 2451 | ; jmp reach_stack_stc 2452 | 2453 | ; ************************* INT 16h handler - keyboard 2454 | 2455 | int16: 2456 | cmp ah, 0x00 ; Get keystroke (remove from buffer) 2457 | je kb_getkey 2458 | cmp ah, 0x01 ; Check for keystroke (do not remove from buffer) 2459 | je kb_checkkey 2460 | cmp ah, 0x02 ; Check shift flags 2461 | je kb_shiftflags 2462 | cmp ah, 0x12 ; Check shift flags 2463 | je kb_extshiftflags 2464 | 2465 | iret 2466 | 2467 | kb_getkey: 2468 | 2469 | push es 2470 | push bx 2471 | push cx 2472 | push dx 2473 | 2474 | mov bx, 0x40 2475 | mov es, bx 2476 | 2477 | kb_gkblock: 2478 | 2479 | cli 2480 | 2481 | mov cx, [es:kbbuf_tail-bios_data] 2482 | mov bx, [es:kbbuf_head-bios_data] 2483 | mov dx, [es:bx] 2484 | 2485 | sti 2486 | 2487 | ; Wait until there is a key in the buffer 2488 | cmp cx, bx 2489 | je kb_gkblock 2490 | 2491 | add word [es:kbbuf_head-bios_data], 2 2492 | call kb_adjust_buf 2493 | 2494 | mov ah, dh 2495 | mov al, dl 2496 | 2497 | pop dx 2498 | pop cx 2499 | pop bx 2500 | pop es 2501 | 2502 | iret 2503 | 2504 | kb_checkkey: 2505 | 2506 | push es 2507 | push bx 2508 | push cx 2509 | push dx 2510 | 2511 | mov bx, 0x40 2512 | mov es, bx 2513 | 2514 | mov cx, [es:kbbuf_tail-bios_data] 2515 | mov bx, [es:kbbuf_head-bios_data] 2516 | mov dx, [es:bx] 2517 | 2518 | sti 2519 | 2520 | ; Check if there is a key in the buffer. ZF is set if there is none. 2521 | cmp cx, bx 2522 | 2523 | mov ah, dh 2524 | mov al, dl 2525 | 2526 | pop dx 2527 | pop cx 2528 | pop bx 2529 | pop es 2530 | 2531 | retf 2 ; NEED TO FIX THIS!! 2532 | 2533 | kb_shiftflags: 2534 | 2535 | push es 2536 | push bx 2537 | 2538 | mov bx, 0x40 2539 | mov es, bx 2540 | 2541 | mov al, [es:keyflags1-bios_data] 2542 | 2543 | pop bx 2544 | pop es 2545 | 2546 | iret 2547 | 2548 | kb_extshiftflags: 2549 | 2550 | push es 2551 | push bx 2552 | 2553 | mov bx, 0x40 2554 | mov es, bx 2555 | 2556 | mov al, [es:keyflags1-bios_data] 2557 | mov ah, al 2558 | 2559 | pop bx 2560 | pop es 2561 | 2562 | iret 2563 | 2564 | ; ************************* INT 17h handler - printer 2565 | 2566 | int17: 2567 | cmp ah, 0x01 2568 | je int17_initprint ; Initialise printer 2569 | 2570 | jmp reach_stack_stc 2571 | 2572 | int17_initprint: 2573 | 2574 | mov ah, 1 ; No printer 2575 | jmp reach_stack_stc 2576 | 2577 | ; ************************* INT 19h = reboot 2578 | 2579 | int19: 2580 | jmp boot 2581 | 2582 | ; ************************* INT 1Ah - clock 2583 | 2584 | int1a: 2585 | cmp ah, 0 2586 | je int1a_getsystime ; Get ticks since midnight (used for RTC time) 2587 | cmp ah, 2 2588 | je int1a_gettime ; Get RTC time (not actually used by DOS) 2589 | cmp ah, 4 2590 | je int1a_getdate ; Get RTC date 2591 | cmp ah, 0x0f 2592 | je int1a_init ; Initialise RTC 2593 | 2594 | iret 2595 | 2596 | int1a_getsystime: 2597 | 2598 | push ax 2599 | push bx 2600 | push ds 2601 | push es 2602 | 2603 | push cs 2604 | push cs 2605 | pop ds 2606 | pop es 2607 | 2608 | mov bx, timetable 2609 | 2610 | extended_get_rtc 2611 | 2612 | mov ax, 182 ; Clock ticks in 10 seconds 2613 | mul word [tm_msec] 2614 | mov bx, 10000 2615 | div bx ; AX now contains clock ticks in milliseconds counter 2616 | mov [tm_msec], ax 2617 | 2618 | mov ax, 182 ; Clock ticks in 10 seconds 2619 | mul word [tm_sec] 2620 | mov bx, 10 2621 | mov dx, 0 2622 | div bx ; AX now contains clock ticks in seconds counter 2623 | mov [tm_sec], ax 2624 | 2625 | mov ax, 1092 ; Clock ticks in a minute 2626 | mul word [tm_min] ; AX now contains clock ticks in minutes counter 2627 | mov [tm_min], ax 2628 | 2629 | mov ax, 65520 ; Clock ticks in an hour 2630 | mul word [tm_hour] ; DX:AX now contains clock ticks in hours counter 2631 | 2632 | add ax, [tm_msec] ; Add milliseconds in to AX 2633 | adc dx, 0 ; Carry into DX if necessary 2634 | add ax, [tm_sec] ; Add seconds in to AX 2635 | adc dx, 0 ; Carry into DX if necessary 2636 | add ax, [tm_min] ; Add minutes in to AX 2637 | adc dx, 0 ; Carry into DX if necessary 2638 | 2639 | push dx 2640 | push ax 2641 | pop dx 2642 | pop cx 2643 | 2644 | pop es 2645 | pop ds 2646 | pop bx 2647 | pop ax 2648 | 2649 | mov al, 0 2650 | iret 2651 | 2652 | int1a_gettime: 2653 | 2654 | ; Return the system time in BCD format. DOS doesn't use this, but we need to return 2655 | ; something or the system thinks there is no RTC. 2656 | 2657 | push ds 2658 | push es 2659 | push ax 2660 | push bx 2661 | 2662 | push cs 2663 | push cs 2664 | pop ds 2665 | pop es 2666 | 2667 | mov bx, timetable 2668 | 2669 | extended_get_rtc 2670 | 2671 | mov ax, 0 2672 | mov cx, [tm_hour] 2673 | call hex_to_bcd 2674 | mov bh, al ; Hour in BCD is in BH 2675 | 2676 | mov ax, 0 2677 | mov cx, [tm_min] 2678 | call hex_to_bcd 2679 | mov bl, al ; Minute in BCD is in BL 2680 | 2681 | mov ax, 0 2682 | mov cx, [tm_sec] 2683 | call hex_to_bcd 2684 | mov dh, al ; Second in BCD is in DH 2685 | 2686 | mov dl, 0 ; Daylight saving flag = 0 always 2687 | 2688 | mov cx, bx ; Hour:minute now in CH:CL 2689 | 2690 | pop bx 2691 | pop ax 2692 | pop es 2693 | pop ds 2694 | 2695 | jmp reach_stack_clc 2696 | 2697 | int1a_getdate: 2698 | 2699 | ; Return the system date in BCD format. 2700 | 2701 | push ds 2702 | push es 2703 | push bx 2704 | push ax 2705 | 2706 | push cs 2707 | push cs 2708 | pop ds 2709 | pop es 2710 | 2711 | mov bx, timetable 2712 | 2713 | extended_get_rtc 2714 | 2715 | mov ax, 0x1900 2716 | mov cx, [tm_year] 2717 | call hex_to_bcd 2718 | mov cx, ax 2719 | push cx 2720 | 2721 | mov ax, 1 2722 | mov cx, [tm_mon] 2723 | call hex_to_bcd 2724 | mov dh, al 2725 | 2726 | mov ax, 0 2727 | mov cx, [tm_mday] 2728 | call hex_to_bcd 2729 | mov dl, al 2730 | 2731 | pop cx 2732 | pop ax 2733 | pop bx 2734 | pop es 2735 | pop ds 2736 | 2737 | jmp reach_stack_clc 2738 | 2739 | int1a_init: 2740 | 2741 | jmp reach_stack_clc 2742 | 2743 | ; ************************* INT 1Ch - the other timer interrupt 2744 | 2745 | int1c: 2746 | 2747 | iret 2748 | 2749 | ; ************************* INT 1Eh - diskette parameter table 2750 | 2751 | int1e: 2752 | 2753 | db 0xdf ; Step rate 2ms, head unload time 240ms 2754 | db 0x02 ; Head load time 4 ms, non-DMA mode 0 2755 | db 0x25 ; Byte delay until motor turned off 2756 | db 0x02 ; 512 bytes per sector 2757 | int1e_spt db 18 ; 18 sectors per track (1.44MB) 2758 | db 0x1B ; Gap between sectors for 3.5" floppy 2759 | db 0xFF ; Data length (ignored) 2760 | db 0x54 ; Gap length when formatting 2761 | db 0xF6 ; Format filler byte 2762 | db 0x0F ; Head settle time (1 ms) 2763 | db 0x08 ; Motor start time in 1/8 seconds 2764 | 2765 | ; ************************* INT 41h - hard disk parameter table 2766 | 2767 | int41: 2768 | 2769 | int41_max_cyls dw 0 2770 | int41_max_heads db 0 2771 | dw 0 2772 | dw 0 2773 | db 0 2774 | db 11000000b 2775 | db 0 2776 | db 0 2777 | db 0 2778 | dw 0 2779 | int41_max_sect db 0 2780 | db 0 2781 | 2782 | ; ************************* ROM configuration table 2783 | 2784 | rom_config dw 16 ; 16 bytes following 2785 | db 0xfe ; Model 2786 | db 'A' ; Submodel 2787 | db 'C' ; BIOS revision 2788 | db 0b00100000 ; Feature 1 2789 | db 0b00000000 ; Feature 2 2790 | db 0b00000000 ; Feature 3 2791 | db 0b00000000 ; Feature 4 2792 | db 0b00000000 ; Feature 5 2793 | db 0, 0, 0, 0, 0, 0 2794 | 2795 | ; Internal state variables 2796 | 2797 | num_disks dw 0 ; Number of disks present 2798 | hd_secs_hi dw 0 ; Total sectors on HD (high word) 2799 | hd_secs_lo dw 0 ; Total sectors on HD (low word) 2800 | hd_max_sector dw 0 ; Max sector number on HD 2801 | hd_max_track dw 0 ; Max track number on HD 2802 | hd_max_head dw 0 ; Max head number on HD 2803 | drive_tracks_temp dw 0 2804 | drive_sectors_temp dw 0 2805 | drive_heads_temp dw 0 2806 | drive_num_temp dw 0 2807 | boot_state db 0 2808 | cga_refresh_reg db 0 2809 | 2810 | ; Default interrupt handlers 2811 | 2812 | int0: 2813 | int1: 2814 | int2: 2815 | int3: 2816 | int4: 2817 | int5: 2818 | int6: 2819 | intb: 2820 | intc: 2821 | intd: 2822 | inte: 2823 | intf: 2824 | int18: 2825 | int1b: 2826 | int1d: 2827 | 2828 | iret 2829 | 2830 | ; ************ Function call library ************ 2831 | 2832 | ; 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. 2833 | 2834 | hex_to_bcd: 2835 | 2836 | push bx 2837 | 2838 | jcxz h2bfin 2839 | 2840 | h2bloop: 2841 | 2842 | inc ax 2843 | 2844 | ; First process the low nibble of AL 2845 | mov bh, al 2846 | and bh, 0x0f 2847 | cmp bh, 0x0a 2848 | jne c1 2849 | add ax, 0x0006 2850 | 2851 | ; Then the high nibble of AL 2852 | c1: 2853 | mov bh, al 2854 | and bh, 0xf0 2855 | cmp bh, 0xa0 2856 | jne c2 2857 | add ax, 0x0060 2858 | 2859 | ; Then the low nibble of AH 2860 | c2: 2861 | mov bh, ah 2862 | and bh, 0x0f 2863 | cmp bh, 0x0a 2864 | jne c3 2865 | add ax, 0x0600 2866 | 2867 | c3: 2868 | loop h2bloop 2869 | h2bfin: 2870 | pop bx 2871 | ret 2872 | 2873 | ; Takes a number in AL (from 0 to 99), and outputs the value in decimal using extended_putchar_al. 2874 | 2875 | puts_decimal_al: 2876 | 2877 | push ax 2878 | 2879 | aam 2880 | add ax, 0x3030 ; '00' 2881 | 2882 | cmp ah, 0x30 2883 | je pda_2nd ; First digit is zero, so print only 2nd digit 2884 | 2885 | xchg ah, al ; First digit is now in AL 2886 | extended_putchar_al ; Print first digit 2887 | xchg ah, al ; Second digit is now in AL 2888 | 2889 | pda_2nd: 2890 | 2891 | extended_putchar_al ; Print second digit 2892 | 2893 | pop ax 2894 | ret 2895 | 2896 | ; Keyboard adjust buffer head and tail. If either head or the tail are at the end of the buffer, reset them 2897 | ; back to the start, since it is a circular buffer. 2898 | 2899 | kb_adjust_buf: 2900 | 2901 | push ax 2902 | push bx 2903 | 2904 | ; Check to see if the head is at the end of the buffer (or beyond). If so, bring it back 2905 | ; to the start 2906 | 2907 | mov ax, [es:kbbuf_end_ptr-bios_data] 2908 | cmp [es:kbbuf_head-bios_data], ax 2909 | jnge kb_adjust_tail 2910 | 2911 | mov bx, [es:kbbuf_start_ptr-bios_data] 2912 | mov [es:kbbuf_head-bios_data], bx 2913 | 2914 | kb_adjust_tail: 2915 | 2916 | ; Check to see if the tail is at the end of the buffer (or beyond). If so, bring it back 2917 | ; to the start 2918 | 2919 | mov ax, [es:kbbuf_end_ptr-bios_data] 2920 | cmp [es:kbbuf_tail-bios_data], ax 2921 | jnge kb_adjust_done 2922 | 2923 | mov bx, [es:kbbuf_start_ptr-bios_data] 2924 | mov [es:kbbuf_tail-bios_data], bx 2925 | 2926 | kb_adjust_done: 2927 | 2928 | pop bx 2929 | pop ax 2930 | ret 2931 | 2932 | ; Convert CHS disk position (in CH, CL and DH) to absolute sector number in BP:SI 2933 | ; 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 2934 | ; sector number, CL7..6 + CH7..0 is 10-bit cylinder/track number. Hard disks have 512 bytes per sector, but a variable 2935 | ; number of tracks and heads. 2936 | 2937 | chs_to_abs: 2938 | 2939 | push ax 2940 | push bx 2941 | push cx 2942 | push dx 2943 | 2944 | mov [cs:drive_num_temp], dl 2945 | 2946 | ; First, we extract the track number from CH and CL. 2947 | 2948 | push cx 2949 | mov bh, cl 2950 | mov cl, 6 2951 | shr bh, cl 2952 | mov bl, ch 2953 | 2954 | ; Multiply track number (now in BX) by the number of heads 2955 | 2956 | cmp byte [cs:drive_num_temp], 1 ; Floppy disk? 2957 | 2958 | push dx 2959 | 2960 | mov dx, 0 2961 | xchg ax, bx 2962 | 2963 | jne chs_hd 2964 | 2965 | shl ax, 1 ; Multiply by 2 (number of heads on FD) 2966 | push ax 2967 | xor ax, ax 2968 | mov al, [cs:int1e_spt] 2969 | mov [cs:drive_sectors_temp], ax ; Retrieve sectors per track from INT 1E table 2970 | pop ax 2971 | 2972 | jmp chs_continue 2973 | 2974 | chs_hd: 2975 | 2976 | mov bp, [cs:hd_max_head] 2977 | inc bp 2978 | mov [cs:drive_heads_temp], bp 2979 | 2980 | mul word [cs:drive_heads_temp] ; HD, so multiply by computed head count 2981 | 2982 | mov bp, [cs:hd_max_sector] ; We previously calculated maximum HD track, so number of tracks is 1 more 2983 | mov [cs:drive_sectors_temp], bp 2984 | 2985 | chs_continue: 2986 | 2987 | xchg ax, bx 2988 | 2989 | pop dx 2990 | 2991 | xchg dh, dl 2992 | mov dh, 0 2993 | add bx, dx 2994 | 2995 | mov ax, [cs:drive_sectors_temp] 2996 | mul bx 2997 | 2998 | ; Now we extract the sector number (from 1 to 63) - for some reason they start from 1 2999 | 3000 | pop cx 3001 | mov ch, 0 3002 | and cl, 0x3F 3003 | dec cl 3004 | 3005 | add ax, cx 3006 | adc dx, 0 3007 | mov bp, ax 3008 | mov si, dx 3009 | 3010 | ; Now, SI:BP contains offset into disk image file (FD or HD) 3011 | 3012 | pop dx 3013 | pop cx 3014 | pop bx 3015 | pop ax 3016 | ret 3017 | 3018 | ; Clear screen using ANSI codes. Also clear video memory with attribute in BH 3019 | 3020 | clear_screen: 3021 | 3022 | push ax 3023 | 3024 | mov al, 0x1B ; Escape 3025 | extended_putchar_al 3026 | mov al, '[' ; ANSI 3027 | extended_putchar_al 3028 | mov al, 'r' ; Set scrolling window 3029 | extended_putchar_al 3030 | 3031 | mov al, 0x1B ; Escape 3032 | extended_putchar_al 3033 | mov al, '[' ; ANSI 3034 | extended_putchar_al 3035 | mov al, '0' ; Reset attributes 3036 | extended_putchar_al 3037 | mov al, 'm' ; Reset attributes 3038 | extended_putchar_al 3039 | 3040 | push bx 3041 | push cx 3042 | push bp 3043 | push ax 3044 | push es 3045 | 3046 | mov bp, bx ; Convert from CGA to ANSI 3047 | mov cl, 12 3048 | ror bp, cl 3049 | and bp, 7 3050 | mov bl, byte [cs:bp+colour_table] 3051 | add bl, 10 3052 | 3053 | mov al, 0x1B ; Escape 3054 | extended_putchar_al 3055 | mov al, '[' ; ANSI 3056 | extended_putchar_al 3057 | mov al, bl ; Background colour 3058 | call puts_decimal_al 3059 | mov al, 'm' ; Set cursor position command 3060 | extended_putchar_al 3061 | 3062 | mov ax, 0x40 3063 | mov es, ax 3064 | mov byte [es:curpos_x-bios_data], 0 3065 | mov byte [es:crt_curpos_x-bios_data], 0 3066 | mov byte [es:curpos_y-bios_data], 0 3067 | mov byte [es:crt_curpos_y-bios_data], 0 3068 | 3069 | pop es 3070 | pop ax 3071 | pop bp 3072 | pop cx 3073 | pop bx 3074 | 3075 | mov al, 0x1B ; Escape 3076 | extended_putchar_al 3077 | mov al, '[' ; ANSI 3078 | extended_putchar_al 3079 | mov al, '2' ; Clear screen 3080 | extended_putchar_al 3081 | mov al, 'J' 3082 | extended_putchar_al 3083 | 3084 | mov al, 0x1B ; Escape 3085 | extended_putchar_al 3086 | mov al, '[' ; ANSI 3087 | extended_putchar_al 3088 | mov al, '1' ; Cursor row 1 3089 | extended_putchar_al 3090 | mov al, ';' 3091 | extended_putchar_al 3092 | mov al, '1' ; Cursor column 1 3093 | extended_putchar_al 3094 | mov al, 'H' ; Set cursor 3095 | extended_putchar_al 3096 | 3097 | push es 3098 | push di 3099 | push cx 3100 | 3101 | cld 3102 | mov ax, 0xb800 3103 | mov es, ax 3104 | mov di, 0 3105 | mov al, 0 3106 | mov ah, bh 3107 | mov cx, 80*25 3108 | rep stosw 3109 | 3110 | cld 3111 | mov di, 0xc800 3112 | mov es, di 3113 | mov di, 0 3114 | mov cx, 80*25 3115 | rep stosw 3116 | 3117 | cld 3118 | mov di, 0xc000 3119 | mov es, di 3120 | mov di, 0 3121 | mov cx, 80*25 3122 | rep stosw 3123 | 3124 | pop cx 3125 | pop di 3126 | pop es 3127 | 3128 | pop ax 3129 | 3130 | mov byte [cs:vram_dirty], 1 3131 | 3132 | ret 3133 | 3134 | ; Pushes a key press, followed by a key release, event to I/O port 0x60 and calls 3135 | ; INT 9. 3136 | 3137 | keypress_release: 3138 | 3139 | push ax 3140 | 3141 | cmp byte [es:key_now_down-bios_data], 0 3142 | je kpr_no_prev_release 3143 | 3144 | mov al, [es:key_now_down-bios_data] 3145 | add al, 0x80 3146 | call io_key_available 3147 | 3148 | pop ax 3149 | push ax 3150 | 3151 | kpr_no_prev_release: 3152 | 3153 | mov [es:key_now_down-bios_data], al 3154 | call io_key_available 3155 | 3156 | pop ax 3157 | 3158 | ret 3159 | 3160 | ; Sets key available flag on I/O port 0x64, outputs key scan code in AL to I/O port 0x60, and calls INT 9 3161 | 3162 | io_key_available: 3163 | 3164 | push ax 3165 | mov al, 1 3166 | out 0x64, al 3167 | pop ax 3168 | 3169 | out 0x60, al 3170 | int 9 3171 | ret 3172 | 3173 | ; Reaches up into the stack before the end of an interrupt handler, and sets the carry flag 3174 | 3175 | reach_stack_stc: 3176 | 3177 | xchg bp, sp 3178 | or word [bp+4], 1 3179 | xchg bp, sp 3180 | iret 3181 | 3182 | ; Reaches up into the stack before the end of an interrupt handler, and clears the carry flag 3183 | 3184 | reach_stack_clc: 3185 | 3186 | xchg bp, sp 3187 | and word [bp+4], 0xfffe 3188 | xchg bp, sp 3189 | iret 3190 | 3191 | ; Reaches up into the stack before the end of an interrupt handler, and returns with the current 3192 | ; setting of the carry flag 3193 | 3194 | reach_stack_carry: 3195 | 3196 | jc reach_stack_stc 3197 | jmp reach_stack_clc 3198 | 3199 | ; This is the VMEM driver, to support direct video memory access in 80x25 colour CGA mode. 3200 | ; It scans through CGA video memory at address B800:0, and if there is anything there (i.e. 3201 | ; applications are doing direct video memory writes), converts the buffer to a sequence of 3202 | ; ANSI terminal codes to render the screen output. 3203 | ; 3204 | ; Note: this destroys all registers. It is the responsibility of the caller to save/restore 3205 | ; them. 3206 | 3207 | vmem_driver_entry: 3208 | 3209 | cmp byte [cs:in_update], 1 3210 | je just_finish ; If we are already in the middle of an update, skip. Needed for re-entrancy 3211 | 3212 | inc byte [cs:int8_ctr] 3213 | cmp byte [cs:int8_ctr], 8 ; Only do this once every 8 timer ticks 3214 | jne just_finish 3215 | 3216 | gmode_test: 3217 | 3218 | mov byte [cs:int8_ctr], 0 3219 | mov dx, 0x3b8 ; Do not update if in Hercules graphics mode 3220 | in al, dx 3221 | test al, 2 3222 | jz vram_zero_check 3223 | 3224 | just_finish: 3225 | 3226 | ret 3227 | 3228 | vram_zero_check: ; Check if video memory is blank - if so, do nothing 3229 | 3230 | mov byte [cs:in_update], 1 3231 | 3232 | sti 3233 | 3234 | mov bx, 0x40 3235 | mov ds, bx 3236 | 3237 | mov di, [vmem_offset-bios_data] ; Adjust for CRTC video memory offset register 3238 | shl di, 1 3239 | push di 3240 | 3241 | mov bx, 0xb800 3242 | mov es, bx 3243 | mov cx, 0x7d0 3244 | mov ax, 0x0700 3245 | 3246 | cld 3247 | repz scasw 3248 | pop di 3249 | je vmem_done ; Nothing has been written to video RAM - no need to update 3250 | 3251 | cmp byte [cs:vram_dirty], 1 ; Cleared screen so always need to update 3252 | je vram_update 3253 | 3254 | mov bx, 0xc800 3255 | mov ds, bx 3256 | mov si, 0 3257 | mov cx, 0x7d0 3258 | 3259 | cld 3260 | repz cmpsw 3261 | jne vram_update ; Video RAM is changed - need to update 3262 | 3263 | mov bx, 0x40 3264 | mov ds, bx 3265 | mov bh, [crt_curpos_y-bios_data] 3266 | mov bl, [crt_curpos_x-bios_data] 3267 | 3268 | cmp bh, [cs:crt_curpos_y_last] 3269 | jne restore_cursor ; Cursor position changed (but nothing else) so update just that 3270 | cmp bl, [cs:crt_curpos_x_last] 3271 | jne restore_cursor 3272 | 3273 | jmp vmem_done 3274 | 3275 | vram_update: 3276 | 3277 | mov bx, 0x40 3278 | mov es, bx 3279 | 3280 | push cs 3281 | pop ds 3282 | 3283 | mov byte [int_curpos_x], 0xff 3284 | mov byte [int_curpos_y], 0xff 3285 | 3286 | cmp byte [es:cursor_visible-bios_data], 0 3287 | je dont_hide_cursor 3288 | 3289 | call ansi_hide_cursor 3290 | 3291 | dont_hide_cursor: 3292 | 3293 | mov byte [last_attrib], 0xff 3294 | 3295 | mov bx, 0x40 3296 | mov es, bx 3297 | 3298 | mov di, [es:vmem_offset-bios_data] ; Adjust for CRTC video memory offset register 3299 | shl di, 1 3300 | sub di, 2 ; Combined offset 3301 | 3302 | mov bx, 0xb800 3303 | mov es, bx 3304 | 3305 | ; Set up the initial cursor coordinates. Since the first thing we do is increment the cursor 3306 | ; position, this initial position is actually off the screen 3307 | 3308 | mov bp, -1 ; Row number 3309 | mov si, 79 ; Column number 3310 | 3311 | disp_loop: 3312 | 3313 | ; Advance to next column 3314 | 3315 | add di, 2 3316 | inc si 3317 | cmp si, 80 3318 | jne cont 3319 | 3320 | ; Column is 80, so set to 0 and advance a line 3321 | 3322 | loop_next_line: 3323 | 3324 | mov si, 0 3325 | inc bp 3326 | 3327 | ; Bottom of the screen reached already? If so, we're done 3328 | 3329 | cmp bp, 25 3330 | je restore_attrib 3331 | 3332 | ; See if this line has changed in video RAM 3333 | 3334 | cmp byte [cs:vram_dirty], 1 3335 | je cont 3336 | 3337 | push si 3338 | push di 3339 | 3340 | mov bx, 0xb800 3341 | mov ds, bx 3342 | mov bx, 0xc800 3343 | mov es, bx 3344 | mov si, di 3345 | 3346 | push es 3347 | mov bx, 0x40 3348 | mov es, bx 3349 | sub di, [es:vmem_offset-bios_data] ; Adjust for CRTC video memory offset register 3350 | sub di, [es:vmem_offset-bios_data] 3351 | pop es 3352 | 3353 | mov cx, 80 ; One row's worth of characters 3354 | 3355 | cld 3356 | repz cmpsw 3357 | pop di 3358 | pop si 3359 | 3360 | je vmem_next_line ; This line is unchanged in video RAM, so do not update 3361 | 3362 | vmem_copy_buf: 3363 | 3364 | ; Copy the changed line to our double buffer at C800:0 3365 | 3366 | push cx 3367 | push si 3368 | push di 3369 | 3370 | push es 3371 | mov bx, 0x40 3372 | mov es, bx 3373 | mov si, di 3374 | sub di, [es:vmem_offset-bios_data] ; Adjust for CRTC video memory offset register 3375 | sub di, [es:vmem_offset-bios_data] 3376 | pop es 3377 | 3378 | mov cx, 80 ; One row's worth of characters 3379 | cld 3380 | rep movsw 3381 | 3382 | pop di 3383 | pop si 3384 | pop cx 3385 | 3386 | ; We want to start the update at the first character which differs - so calculate its position. 3387 | 3388 | mov bx, 79 3389 | sub bx, cx 3390 | 3391 | add di, bx 3392 | add di, bx 3393 | add si, bx 3394 | 3395 | push ds 3396 | pop es ; Set ES back to B800 3397 | 3398 | jmp cont 3399 | 3400 | vmem_next_line: 3401 | 3402 | add di, 160 3403 | jmp loop_next_line ; Line is unchanged in video RAM 3404 | 3405 | cont: 3406 | push cs 3407 | pop ds 3408 | 3409 | cmp byte [es:di], 0 ; Ignore null characters in video memory 3410 | je disp_loop 3411 | 3412 | mov ax, bp 3413 | mov bx, si 3414 | mov dh, al 3415 | mov dl, bl 3416 | 3417 | cmp dh, [int_curpos_y] ; Same row as the last time? 3418 | jne ansi_set_cur_pos 3419 | push dx 3420 | dec dl 3421 | cmp dl, [int_curpos_x] ; One column to the right since the last time? 3422 | pop dx 3423 | je skip_set_cur_pos 3424 | 3425 | ansi_set_cur_pos: 3426 | 3427 | mov al, 0x1B ; Escape 3428 | extended_putchar_al 3429 | mov al, '[' ; ANSI 3430 | extended_putchar_al 3431 | mov al, dh ; Row number 3432 | inc al 3433 | call puts_decimal_al 3434 | mov al, ';' ; ANSI 3435 | extended_putchar_al 3436 | mov al, dl ; Column number 3437 | inc al 3438 | call puts_decimal_al 3439 | mov al, 'H' ; Set cursor position command 3440 | extended_putchar_al 3441 | 3442 | mov [int_curpos_y], dh 3443 | 3444 | skip_set_cur_pos: 3445 | 3446 | mov [int_curpos_x], dl 3447 | 3448 | mov dl, [es:di+1] 3449 | cmp dl, [last_attrib] 3450 | je skip_attrib 3451 | 3452 | mov [last_attrib], dl 3453 | 3454 | mov al, 0x1B ; Escape 3455 | extended_putchar_al 3456 | mov al, '[' ; ANSI 3457 | extended_putchar_al 3458 | 3459 | mov al, dl 3460 | and al, 8 ; Bright attribute now in AL 3461 | cpu 186 3462 | shr al, 3 3463 | cpu 8086 3464 | 3465 | call puts_decimal_al 3466 | mov al, ';' 3467 | extended_putchar_al 3468 | 3469 | push dx 3470 | 3471 | and dl, 7 ; Foreground colour now in DL 3472 | mov bx, colour_table 3473 | mov al, dl 3474 | xlat 3475 | 3476 | call puts_decimal_al 3477 | mov al, ';' 3478 | extended_putchar_al 3479 | 3480 | pop dx 3481 | 3482 | cpu 186 3483 | shr dl, 4 3484 | cpu 8086 3485 | and dl, 7 ; Background colour now in DL 3486 | 3487 | mov al, dl 3488 | xlat 3489 | 3490 | add al, 10 3491 | call puts_decimal_al 3492 | mov al, 'm' ; Set cursor attribute command 3493 | extended_putchar_al 3494 | 3495 | skip_attrib: 3496 | 3497 | mov al, [es:di] 3498 | 3499 | cmp al, 32 ; Non-printable ASCII? (< 32 decimal) 3500 | jae just_show_it 3501 | 3502 | mov bx, low_ascii_conv 3503 | cs xlat ; Convert to printable representation (mostly spaces) 3504 | 3505 | just_show_it: 3506 | 3507 | extended_putchar_al 3508 | 3509 | jmp disp_loop 3510 | 3511 | restore_attrib: 3512 | 3513 | mov al, 0x1B ; Escape 3514 | extended_putchar_al 3515 | mov al, '[' ; ANSI 3516 | extended_putchar_al 3517 | mov al, '0' ; Reset attributes 3518 | extended_putchar_al 3519 | mov al, 'm' 3520 | extended_putchar_al 3521 | 3522 | restore_cursor: 3523 | 3524 | ; On a real PC, the 6845 CRT cursor position registers take place over the BIOS 3525 | ; Data Area ones. So, if the cursor is not off the screen, set it to the CRT 3526 | ; position. 3527 | 3528 | mov bx, 0x40 3529 | mov ds, bx 3530 | 3531 | mov bh, [crt_curpos_y-bios_data] 3532 | mov bl, [crt_curpos_x-bios_data] 3533 | mov [cs:crt_curpos_y_last], bh 3534 | mov [cs:crt_curpos_x_last], bl 3535 | 3536 | cmp bh, 24 3537 | ja vmem_end_hidden_cursor 3538 | cmp bl, 79 3539 | ja vmem_end_hidden_cursor 3540 | 3541 | mov al, 0x1B ; ANSI 3542 | extended_putchar_al 3543 | mov al, '[' ; ANSI 3544 | extended_putchar_al 3545 | mov al, bh ; Row number 3546 | inc al 3547 | call puts_decimal_al 3548 | mov al, ';' ; ANSI 3549 | extended_putchar_al 3550 | mov al, bl ; Column number 3551 | inc al 3552 | call puts_decimal_al 3553 | mov al, 'H' ; Set cursor position command 3554 | extended_putchar_al 3555 | 3556 | restore_cursor_visible: 3557 | 3558 | cmp byte [cursor_visible-bios_data], 1 3559 | jne vmem_end_hidden_cursor 3560 | 3561 | call ansi_show_cursor 3562 | jmp vmem_done 3563 | 3564 | vmem_end_hidden_cursor: 3565 | 3566 | call ansi_hide_cursor 3567 | 3568 | vmem_done: 3569 | 3570 | mov byte [cs:vram_dirty], 0 3571 | mov byte [cs:in_update], 0 3572 | ret 3573 | 3574 | ; Show cursor using ANSI codes 3575 | 3576 | ansi_show_cursor: 3577 | 3578 | mov al, 0x1B 3579 | extended_putchar_al 3580 | mov al, '[' 3581 | extended_putchar_al 3582 | mov al, '?' 3583 | extended_putchar_al 3584 | mov al, '2' 3585 | extended_putchar_al 3586 | mov al, '5' 3587 | extended_putchar_al 3588 | mov al, 'h' 3589 | extended_putchar_al 3590 | 3591 | ret 3592 | 3593 | ; Hide cursor using ANSI codes 3594 | 3595 | ansi_hide_cursor: 3596 | 3597 | mov al, 0x1B 3598 | extended_putchar_al 3599 | mov al, '[' 3600 | extended_putchar_al 3601 | mov al, '?' 3602 | extended_putchar_al 3603 | mov al, '2' 3604 | extended_putchar_al 3605 | mov al, '5' 3606 | extended_putchar_al 3607 | mov al, 'l' 3608 | extended_putchar_al 3609 | 3610 | ret 3611 | 3612 | ; **************************************************************************************** 3613 | ; That's it for the code. Now, the data tables follow. 3614 | ; **************************************************************************************** 3615 | 3616 | ; Standard PC-compatible BIOS data area - to copy to 40:0 3617 | 3618 | bios_data: 3619 | 3620 | com1addr dw 0 3621 | com2addr dw 0 3622 | com3addr dw 0 3623 | com4addr dw 0 3624 | lpt1addr dw 0 3625 | lpt2addr dw 0 3626 | lpt3addr dw 0 3627 | lpt4addr dw 0 3628 | equip dw 0b0000000000100001 3629 | ;equip dw 0b0000000100100001 3630 | db 0 3631 | memsize dw 0x280 3632 | db 0 3633 | db 0 3634 | keyflags1 db 0 3635 | keyflags2 db 0 3636 | db 0 3637 | kbbuf_head dw kbbuf-bios_data 3638 | kbbuf_tail dw kbbuf-bios_data 3639 | kbbuf: times 32 db 'X' 3640 | drivecal db 0 3641 | diskmotor db 0 3642 | motorshutoff db 0x07 3643 | disk_laststatus db 0 3644 | times 7 db 0 3645 | vidmode db 0x03 3646 | vid_cols dw 80 3647 | page_size dw 0x1000 3648 | dw 0 3649 | curpos_x db 0 3650 | curpos_y db 0 3651 | times 7 dw 0 3652 | cur_v_end db 7 3653 | cur_v_start db 6 3654 | disp_page db 0 3655 | crtport dw 0x3d4 3656 | db 10 3657 | db 0 3658 | times 5 db 0 3659 | clk_dtimer dd 0 3660 | clk_rollover db 0 3661 | ctrl_break db 0 3662 | soft_rst_flg dw 0x1234 3663 | db 0 3664 | num_hd db 0 3665 | db 0 3666 | db 0 3667 | dd 0 3668 | dd 0 3669 | kbbuf_start_ptr dw 0x001e 3670 | kbbuf_end_ptr dw 0x003e 3671 | vid_rows db 25 ; at 40:84 3672 | db 0 3673 | db 0 3674 | vidmode_opt db 0 ; 0x70 3675 | db 0 ; 0x89 3676 | db 0 ; 0x51 3677 | db 0 ; 0x0c 3678 | db 0 3679 | db 0 3680 | db 0 3681 | db 0 3682 | db 0 3683 | db 0 3684 | db 0 3685 | db 0 3686 | db 0 3687 | db 0 3688 | db 0 3689 | kb_mode db 0 3690 | kb_led db 0 3691 | db 0 3692 | db 0 3693 | db 0 3694 | db 0 3695 | boot_device db 0 3696 | crt_curpos_x db 0 3697 | crt_curpos_y db 0 3698 | key_now_down db 0 3699 | next_key_fn db 0 3700 | cursor_visible db 1 3701 | escape_flag_last db 0 3702 | next_key_alt db 0 3703 | escape_flag db 0 3704 | notranslate_flg db 0 3705 | this_keystroke db 0 3706 | this_keystroke_ext db 0 3707 | timer0_freq dw 0xffff ; PIT channel 0 (55ms) 3708 | timer2_freq dw 0 ; PIT channel 2 3709 | cga_vmode db 0 3710 | vmem_offset dw 0 ; Video RAM offset 3711 | ending: times (0xff-($-com1addr)) db 0 3712 | 3713 | ; Keyboard scan code tables 3714 | 3715 | 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 3716 | 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 3717 | 3718 | ; Interrupt vector table - to copy to 0:0 3719 | 3720 | int_table dw int0 3721 | dw 0xf000 3722 | dw int1 3723 | dw 0xf000 3724 | dw int2 3725 | dw 0xf000 3726 | dw int3 3727 | dw 0xf000 3728 | dw int4 3729 | dw 0xf000 3730 | dw int5 3731 | dw 0xf000 3732 | dw int6 3733 | dw 0xf000 3734 | dw int7 3735 | dw 0xf000 3736 | dw int8 3737 | dw 0xf000 3738 | dw int9 3739 | dw 0xf000 3740 | dw inta 3741 | dw 0xf000 3742 | dw intb 3743 | dw 0xf000 3744 | dw intc 3745 | dw 0xf000 3746 | dw intd 3747 | dw 0xf000 3748 | dw inte 3749 | dw 0xf000 3750 | dw intf 3751 | dw 0xf000 3752 | dw int10 3753 | dw 0xf000 3754 | dw int11 3755 | dw 0xf000 3756 | dw int12 3757 | dw 0xf000 3758 | dw int13 3759 | dw 0xf000 3760 | dw int14 3761 | dw 0xf000 3762 | dw int15 3763 | dw 0xf000 3764 | dw int16 3765 | dw 0xf000 3766 | dw int17 3767 | dw 0xf000 3768 | dw int18 3769 | dw 0xf000 3770 | dw int19 3771 | dw 0xf000 3772 | dw int1a 3773 | dw 0xf000 3774 | dw int1b 3775 | dw 0xf000 3776 | dw int1c 3777 | dw 0xf000 3778 | dw int1d 3779 | dw 0xf000 3780 | dw int1e 3781 | 3782 | itbl_size dw $-int_table 3783 | 3784 | ; Conversion from CGA video memory colours to ANSI colours 3785 | 3786 | colour_table db 30, 34, 32, 36, 31, 35, 33, 37 3787 | 3788 | ; Conversion from non-printable low ASCII to printable 3789 | 3790 | low_ascii_conv db ' ', 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, '><|!|$', 250, '|^v><--^v' 3791 | 3792 | ; Conversion from UNIX cursor keys/SDL keycodes to scancodes 3793 | 3794 | unix_cursor_xlt db 0x48, 0x50, 0x4d, 0x4b 3795 | 3796 | ; Conversion from SDL keycodes to Home/End/PgUp/PgDn scancodes 3797 | 3798 | pgup_pgdn_xlt db 0x47, 0x4f, 0x49, 0x51 3799 | 3800 | ; Internal variables for VMEM driver 3801 | 3802 | int8_ctr db 0 3803 | in_update db 0 3804 | vram_dirty db 0 3805 | last_attrib db 0 3806 | int_curpos_x db 0 3807 | int_curpos_y db 0 3808 | crt_curpos_x_last db 0 3809 | crt_curpos_y_last db 0 3810 | 3811 | ; INT 8 millisecond counter 3812 | 3813 | last_int8_msec dw 0 3814 | last_key_sdl db 0 3815 | 3816 | ; Now follow the tables for instruction decode helping 3817 | 3818 | ; R/M mode tables 3819 | 3820 | rm_mode0_reg1 db 3, 3, 5, 5, 6, 7, 12, 3 3821 | rm_mode012_reg2 db 6, 7, 6, 7, 12, 12, 12, 12 3822 | rm_mode0_disp db 0, 0, 0, 0, 0, 0, 1, 0 3823 | rm_mode0_dfseg db 11, 11, 10, 10, 11, 11, 11, 11 3824 | 3825 | rm_mode12_reg1 db 3, 3, 5, 5, 6, 7, 5, 3 3826 | rm_mode12_disp db 1, 1, 1, 1, 1, 1, 1, 1 3827 | rm_mode12_dfseg db 11, 11, 10, 10, 11, 11, 10, 11 3828 | 3829 | ; Opcode decode tables 3830 | 3831 | 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 3832 | 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 3833 | 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 3834 | 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 3835 | 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 3836 | 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 3837 | 3838 | flags_mult db 0, 2, 4, 6, 7, 8, 9, 10, 11 3839 | 3840 | jxx_dec_a db 48, 40, 43, 40, 44, 41, 49, 49 3841 | jxx_dec_b db 49, 49, 49, 43, 49, 49, 49, 43 3842 | jxx_dec_c db 49, 49, 49, 49, 49, 49, 44, 44 3843 | jxx_dec_d db 49, 49, 49, 49, 49, 49, 48, 48 3844 | 3845 | 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 3846 | 3847 | ; This is the format of the 36-byte tm structure, returned by the emulator's RTC query call 3848 | 3849 | timetable: 3850 | 3851 | tm_sec equ $ 3852 | tm_min equ $+4 3853 | tm_hour equ $+8 3854 | tm_mday equ $+12 3855 | tm_mon equ $+16 3856 | tm_year equ $+20 3857 | tm_wday equ $+24 3858 | tm_yday equ $+28 3859 | tm_dst equ $+32 3860 | tm_msec equ $+36 -------------------------------------------------------------------------------- /docs/8086tiny.css: -------------------------------------------------------------------------------- 1 | /* 2 | CSS Credit: http://www.templatemo.com/ 3 | */ 4 | body { 5 | margin:0; 6 | padding:0; 7 | line-height: 1.5em; 8 | font-family: "Trebuchet MS", Verdana, Helvetica, Arial; 9 | font-size: 12px; 10 | color: #000000; 11 | background: #999999 url(images/templatemo_bg.gif); 12 | } 13 | a:link, a:visited { color: #0066CC; text-decoration: none} 14 | a:active, a:hover { color: #008800; text-decoration: underline} 15 | 16 | #templatemo_container_wrapper { 17 | background: url(images/templatemo_side_bg.gif) repeat-x; 18 | padding-left: 2px; 19 | } 20 | #templatemo_container { 21 | width: 809px; 22 | margin: 0px auto; 23 | background: url(images/templatemo_content_bg.gif); 24 | } 25 | #templatemo_top { 26 | clear: left; 27 | height: 25px; /* 'padding-top' + 'height' must be equal to the 'background image height' */ 28 | padding-top: 18px; 29 | padding-left: 30px; 30 | color: #0000bf; 31 | background: url(images/templatemo_top_bg.gif) no-repeat bottom; 32 | } 33 | #templatemo_header { 34 | clear: left; 35 | height: 113px; 36 | text-align: center; 37 | background: url(images/templatemo_header_bg.gif) no-repeat; 38 | } 39 | #inner_header { 40 | height: 88px; 41 | background: url(images/templatemo_header.jpg) no-repeat center center; 42 | } 43 | #templatemo_left_column { 44 | clear: left; 45 | float: left; 46 | width: 540px; 47 | padding-left: 20px; 48 | } 49 | #templatemo_right_column { 50 | float: right; 51 | width: 216px; 52 | padding-right: 15px; 53 | } 54 | #templatemo_footer { 55 | clear: both; 56 | padding-top: 18px; 57 | height: 37px; 58 | text-align: center; 59 | font-size: 11px; 60 | background: url(images/templatemo_footer_bg.gif) no-repeat; 61 | color: #666666; 62 | } 63 | #templatemo_footer a { 64 | color: #666666; 65 | } 66 | #templatemo_site_title { 67 | padding-top: 65px; 68 | font-weight: bold; 69 | font-size: 32px; 70 | color: #FFFFFF; 71 | } 72 | .site_slogan_center { 73 | float:left; 74 | display:block; 75 | width:50px; 76 | height:25px; 77 | text-indent:-999px; 78 | cursor:default; 79 | } 80 | #templatemo_site_slogan { 81 | padding-top: 14px; 82 | font-weight: bold; 83 | font-size: 13px; 84 | color: #AAFFFF; 85 | } 86 | .templatemo_spacer { 87 | clear: left; 88 | height: 18px; 89 | } 90 | .templatemo_pic { 91 | float: left; 92 | margin-right: 10px; 93 | margin-bottom: 10px; 94 | border: 1px solid #000000; 95 | } 96 | .section_box { 97 | margin: 10px; 98 | padding: 10px; 99 | border: 1px dashed #CCCCCC; 100 | background: #F2F2F2; 101 | } 102 | .section_box2 { 103 | clear: left; 104 | margin-top: 10px; 105 | background: #F6F6F6; 106 | color: #000000; 107 | } 108 | .text_area { 109 | padding: 10px; 110 | } 111 | .publish_date { 112 | clear: both; 113 | margin-top: 10px; 114 | color: #999999; 115 | font-size: 11px; 116 | font-weight: bold; 117 | } 118 | .title { 119 | padding-bottom: 12px; 120 | font-size: 18px; 121 | font-weight: bold; 122 | color: #0066CC; 123 | } 124 | .subtitle { 125 | padding-bottom: 6px; 126 | font-size: 14px; 127 | font-weight: bold; 128 | color: #666666; 129 | } 130 | .post_title { 131 | padding: 6px; 132 | padding-left: 10px; 133 | background: #DDEEFF; 134 | font-size: 14px; 135 | font-weight: bold; 136 | color: #0066CC; 137 | } 138 | .templatemo_menu { 139 | list-style-type: none; 140 | margin: 10px; 141 | margin-top: 0px; 142 | padding: 0px; 143 | width: 195px; 144 | } 145 | .templatemo_menu li a{ 146 | background: #F4F4F4 url(images/button_default.gif) no-repeat; 147 | font-size: 13px; 148 | font-weight: bold; 149 | color: #0066CC; 150 | display: block; 151 | width: auto; 152 | margin-bottom: 2px; 153 | padding: 5px; 154 | padding-left: 12px; 155 | text-decoration: none; 156 | } 157 | * html .templatemo_menu li a{ 158 | width: 190px; 159 | } 160 | .templatemo_menu li a:visited, .templatemo_menu li a:active{ 161 | color: #0066CC; 162 | } 163 | .templatemo_menu li a:hover{ 164 | background: #EEEEEE url(images/button_active.gif) no-repeat; 165 | color: #FF3333; 166 | } -------------------------------------------------------------------------------- /docs/doc.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 8086tiny: a tiny PC emulator/virtual machine - Documentation 6 | 7 | 8 | 9 | 10 | 11 | 14 |
15 |
16 |
17 |
18 | 8086tiny · Documentation 19 |
20 |
21 | 22 |
23 | 24 |
25 |
26 | 27 |
Documentation
28 | 29 |

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:

30 | 31 |
    32 |
  • Intel 8086/186 CPU
  • 33 |
  • 1MB RAM
  • 34 |
  • 3.5" floppy disk controller (1.44MB/720KB)
  • 35 |
  • Fixed disk controller (supports a single hard drive up to 528MB)
  • 36 |
  • 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
  • 37 |
  • Accurate programmable interrupt timer (PIT)
  • 38 |
  • Keyboard controller with 83-key XT-style keyboard
  • 39 |
  • Real-time clock
  • 40 |
  • PC speaker
  • 41 |
42 | 43 |

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).

44 |

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

45 | 46 |
47 |
Build Instructions
48 |
49 |

50 | 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++. 51 |

52 |
    53 |
  • Running make compiles the full 8086tiny distribution, which includes audio and CGA/Hercules graphics support via SDL.
  • 54 |
  • To compile for slower platforms (e.g. Raspberry Pi), build with make 8086tiny_slowcpu to increase the graphics emulation frame rate.
  • 55 |
  • 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.
  • 56 |
57 |
58 |
59 | 60 |
61 |
Usage
62 |
63 |

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

64 | 65 |

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.

66 | 67 |

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:

68 | 69 | #!/bin/sh
70 | clear
71 | stty cbreak raw -echo min 0
72 | if [ -f hd.img ]
73 | then
74 |   ./8086tiny bios fd.img hd.img
75 | else
76 |   ./8086tiny bios fd.img
77 | fi
78 | stty cooked echo 79 |
80 |
81 |
82 | 83 |
84 |
Building a Hard Disk Image
85 |
86 |

87 | 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. 88 |

89 |

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

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

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). 96 |

97 |
98 |
99 | 100 |
101 |
Keyboard Support
102 |
103 |

104 | 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. 105 |

106 |

107 | 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. 108 |

109 | 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: 110 |

    111 |
  • 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.
  • 112 |
  • 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.
  • 113 |
  • To send Ctrl+A, press Ctrl+F then Ctrl+A. To send Ctrl+F, press Ctrl+F then Ctrl+F.
  • 114 |
  • To send a Page Down key, press Ctrl+F then O. To send a Page Up key, press Ctrl+F then Q.
  • 115 |
116 |

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

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.

118 |
119 |
120 | 121 |
122 |
Floppy Drive Emulation
123 |
124 |

125 | 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). 126 |

127 | 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! 128 |

129 |
130 |
131 | 132 |
133 |
Hard Drive Emulation
134 |
135 |

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

138 | 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. 139 |

140 | 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. 141 |

142 | 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 143 |

144 |
145 |
146 | 147 |
148 |
Text Mode Support
149 |
150 |

151 | 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. 152 |

153 | 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. 154 |

155 | 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.

156 |

157 | 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). 158 |

159 | 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. 160 |

161 |
162 |
163 | 164 |
165 |
Graphics Mode Support
166 |
167 |

168 | 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. 169 |

170 | 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. 171 |

172 | 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. 173 |

174 | 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. 175 |

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

178 |
179 |
180 | 181 |
182 |
Real-Time Clock Support
183 |
184 |

185 | 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. 186 |

187 |
188 |
189 | 190 |
191 |
Hardware Timer Support
192 |
193 |

194 | 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. 195 |

196 |
197 |
198 | 199 |
200 |
PC Speaker Support
201 |
202 |

203 | 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. 204 |

205 |
206 |
207 | 208 |
209 |
BIOS
210 |
211 |

212 | 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. 213 |

214 | 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. 215 | 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. 216 |

217 |
218 |
219 | 220 |
221 |
Memory Map and Register Emulation
222 |
223 |

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

226 | 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. 227 |

228 |
229 |
230 | 231 |
232 |
CPU, Memory and Register Emulation
233 |
234 |

235 | 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. 236 |

237 | 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. 238 |

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

241 |
    242 |
  • 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.
  • 243 |
  • 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.
  • 244 |
  • 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.
  • 245 |
  • 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.
  • 246 |
    • 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.
    • 247 |
    • FLAGS_UPDATE_AO_ARITH (2) signifies that this instruction sets the auxiliary and overflow flags as standard for arithmetic operations.
    • 248 |
    • 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.
    249 |
250 | 251 |

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

    253 |
  • 0F 00 (PUTCHAR_AL) - output character in register AL to terminal
  • 254 |
  • 0F 01 (GET_RTC) - write real-time clock data (as returned by localtime) to memory location ES:BX
  • 255 |
  • 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)
  • 256 |
  • 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
  • 257 |
258 | 259 |

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

260 |

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.

261 |

262 |
263 |
264 | 265 |
266 |
Supported Application Software
267 |
268 |

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

271 | The author has successfully tested a range of software on the emulator. 272 |

    273 |
  • OSes/GUIs
  • 274 |
    • MS-DOS 6.22
    • 275 |
    • FreeDOS 0.82pl3
    • 276 |
    • Linux/ELKS 0.1.4
    • 277 |
    • Windows 3.0
    • 278 |
    • DESQview 2.8
    • 279 |
    • ETEN Chinese System
    280 |
  • Professional software
  • 281 |
    • Lotus 1-2-3 R2.4
    • 282 |
    • AsEasyAs 5.7
    • 283 |
    • Excel 2.1 for Windows
    • 284 |
    • AutoCAD 2.5
    • 285 |
    • WordStar 4
    286 |
  • Programming languages
  • 287 |
    • QBASIC
    • 288 |
    • GWBASIC
    • 289 |
    • Turbo C++
    290 |
  • Games
  • 291 |
    • Carrier Command
    • 292 |
    • Police Quest
    • 293 |
    • SimCity
    • 294 |
    • Alley Cat
    • 295 |
    • MS Flight Simulator 4
    • 296 |
    • Lots of freeware Windows games
    297 |
  • Diagnostic/benchmark software
  • 298 |
    • Manifest
    • 299 |
    • Microsoft MSD
    • 300 |
    • InfoSpot
    • 301 |
    • CheckIt
    302 |
303 |

304 |
305 |
306 | 307 |
308 |
309 | 310 |
311 | 312 | 318 | 319 |
320 |
Author Contact
321 | Adrian Cable
322 | adrian.cable@gmail.com
323 | 324 |

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

325 | 326 |
327 | 328 | 329 | 330 | 331 |
332 | 333 |
334 |
335 | 336 | 339 | 340 |
341 |
342 |
343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /docs/images/QNX2_8086tiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retrohun/8086tiny/8c9a82627d5c054ed6b84ce3e349a458b19b0c6c/docs/images/QNX2_8086tiny.png -------------------------------------------------------------------------------- /docs/images/button_active.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retrohun/8086tiny/8c9a82627d5c054ed6b84ce3e349a458b19b0c6c/docs/images/button_active.gif -------------------------------------------------------------------------------- /docs/images/button_default.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retrohun/8086tiny/8c9a82627d5c054ed6b84ce3e349a458b19b0c6c/docs/images/button_default.gif -------------------------------------------------------------------------------- /docs/images/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retrohun/8086tiny/8c9a82627d5c054ed6b84ce3e349a458b19b0c6c/docs/images/logo.gif -------------------------------------------------------------------------------- /docs/images/templatemo_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retrohun/8086tiny/8c9a82627d5c054ed6b84ce3e349a458b19b0c6c/docs/images/templatemo_bg.gif -------------------------------------------------------------------------------- /docs/images/templatemo_content_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retrohun/8086tiny/8c9a82627d5c054ed6b84ce3e349a458b19b0c6c/docs/images/templatemo_content_bg.gif -------------------------------------------------------------------------------- /docs/images/templatemo_footer_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retrohun/8086tiny/8c9a82627d5c054ed6b84ce3e349a458b19b0c6c/docs/images/templatemo_footer_bg.gif -------------------------------------------------------------------------------- /docs/images/templatemo_header_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retrohun/8086tiny/8c9a82627d5c054ed6b84ce3e349a458b19b0c6c/docs/images/templatemo_header_bg.gif -------------------------------------------------------------------------------- /docs/images/templatemo_side_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retrohun/8086tiny/8c9a82627d5c054ed6b84ce3e349a458b19b0c6c/docs/images/templatemo_side_bg.gif -------------------------------------------------------------------------------- /docs/images/templatemo_top_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retrohun/8086tiny/8c9a82627d5c054ed6b84ce3e349a458b19b0c6c/docs/images/templatemo_top_bg.gif -------------------------------------------------------------------------------- /fd.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retrohun/8086tiny/8c9a82627d5c054ed6b84ce3e349a458b19b0c6c/fd.img -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2014 Adrian Cable - http://www.megalith.co.uk/8086tiny 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /runme: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | clear 3 | stty cbreak raw -echo min 0 4 | if [ -f hd.img ] 5 | then 6 | ./8086tiny bios fd.img hd.img 7 | else 8 | ./8086tiny bios fd.img 9 | fi 10 | stty cooked echo 11 | --------------------------------------------------------------------------------