diff --git a/.gitignore b/.gitignore index fa85fb5..d2e2678 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,7 @@ bios.bin bios.lst tinyxms.sys tinyxms.lst +quitemu.com +quitemu.lst 8086tiny hd.img diff --git a/8086tiny.c b/8086tiny.c index 2d60089..75f551c 100644 --- a/8086tiny.c +++ b/8086tiny.c @@ -13,6 +13,7 @@ #include #include #include +#include #define AMOUNT_XMS_HANDLES 32 #define XMS_REPORTED_FREE (16 * 1024 * 1024) @@ -64,6 +65,7 @@ #ifndef GRAPHICS_UPDATE_DELAY #define GRAPHICS_UPDATE_DELAY 360000 #endif +#define APM_INST_DELAY 1000 #define KEYBOARD_TIMER_UPDATE_DELAY 20000 #define HALT_TIME_MICROSECONDS 100 #define KEYBOARD_TIMER_DELAY_FROM_HALT 1000 @@ -204,11 +206,14 @@ unsigned char mem[RAM_SIZE + 16], io_ports[IO_PORT_COUNT + 16], hlt_this_time, setting_ss, prior_setting_ss, reset_ip_after_rep_trace, shift_count; unsigned short *regs16, reg_ip, seg_override, file_index, wave_counter, reg_ip_before_rep_trace; -unsigned int op_source, op_dest, rm_addr, op_to_addr, op_from_addr, i_data0, i_data1, i_data2, scratch_uint, scratch2_uint, keyboard_timer_inst_counter, graphics_inst_counter, set_flags_type, GRAPHICS_X, GRAPHICS_Y, pixel_colors[16], vmem_ctr; -int op_result, disk[3], scratch_int; +unsigned int op_source, op_dest, rm_addr, op_to_addr, op_from_addr, i_data0, i_data1, i_data2, scratch_uint, scratch2_uint, keyboard_timer_inst_counter, graphics_inst_counter, apm_inst_counter, set_flags_type, GRAPHICS_X, GRAPHICS_Y, pixel_colors[16], vmem_ctr; +int op_result, disk[3], scratch_int, device_status; time_t clock_buf; struct timeb ms_clock; +#define DEVICE_STATUS_GRAPHICS 1 +#define DEVICE_STATUS_FASTCPU 2 + #ifndef NO_GRAPHICS SDL_AudioSpec sdl_audio = {44100, AUDIO_U8, 1, 0, 128}; SDL_Surface *sdl_screen; @@ -219,6 +224,7 @@ unsigned short vid_addr_lookup[VIDEO_RAM_SIZE], cga_colors[4] = {0 /* Black */, // Helper functions void callxms(); +void callapm(); // Set carry flag char set_CF(int new_CF) @@ -355,6 +361,38 @@ code_to_utf8(unsigned char *const buffer, return 3; } +// read/write routine -- Joshua Hudson 2023 +unsigned char readwrite(int handle, unsigned char *buffer, unsigned char nsectors, char mopcode) +{ + if (nsectors == 0) return 0xFF; // not sure if right, DosBOX does this + ssize_t r; + size_t len = (size_t)nsectors << 9; + ssize_t(*rw)(int,unsigned char*,size_t) = (mopcode != 3) ? (ssize_t(*)(int,unsigned char*,size_t))read:(ssize_t(*)(int,unsigned char*,size_t))write; + while (((r = rw(handle, buffer, len)) > 0 || r < 0 && errno == EINTR)) { + if (r < 0) continue; // EINTR + buffer += (size_t)r; + if (!(len -= (size_t)r)) { + // All data transferred + if (mopcode == 3) { +#ifndef _WIN32 + if (fdatasync(handle)) break; // r will not be 0; switches on errno +//#else I'd have to rewrite a whole lot to implement the call on Windows. +#endif + } + return 0; + } + // Network FS had to chunk write down + } + // Error pathway + if (r == 0) return 0x04; // Sector not found + switch (errno) { + case ENODEV: case ENXIO: return 0xAA; // Drive not ready + case EBADF: case EROFS: return mopcode == 3 ? 0x03 /* write protected */ : 0x0A; + case ENOSPC: case EDQUOT: return 0x04; // sparce file and out of disk: best fit is no such sector + default: return 0x0A; // bad sector: it's EIO and there aren't many likely cases + } +} + // Emulator entry point int main(int argc, char **argv) { @@ -810,11 +848,13 @@ int main(int argc, char **argv) CAST(short)mem[SEGREG(REG_ES, REG_BX, 36+)] = ms_clock.millitm; OPCODE 2: // DISK_READ OPCODE_CHAIN 3: // DISK_WRITE - regs8[REG_AL] = ~lseek(disk[regs8[REG_DL]], CAST(unsigned)regs16[REG_BP] << 9, 0) - ? ((char)i_data0 == 3 ? (int(*)())write : (int(*)())read)(disk[regs8[REG_DL]], mem + SEGREG(REG_ES, REG_BX,), regs16[REG_AX]) - : 0; + regs8[REG_AH] = ~lseek(disk[regs8[REG_DL]], CAST(unsigned)regs16[REG_BP] << 9, 0) + ? readwrite(disk[regs8[REG_DL]], mem + SEGREG(REG_ES, REG_BX,), regs8[REG_AL], (char)i_data0) + : (errno == EINVAL) ? 0x04 /* out of range on disk device */ : 0xAA /* no disk */; OPCODE 4: // XMS callxms(); + OPCODE 5: + callapm(); } if ((uint8_t)i_data0 == 11 // ud2 || (uint8_t)i_data0 >= 32) @@ -990,7 +1030,7 @@ int main(int argc, char **argv) graphics_inst_counter = 0; hlt_this_time = 0; // Video card in graphics mode? - if (io_ports[0x3B8] & 2) + if (io_ports[0x3B8] & 2 && device_status & DEVICE_STATUS_GRAPHICS) { // If we don't already have an SDL window open, set it up and compute color and video memory translation tables if (!sdl_screen) @@ -1024,6 +1064,11 @@ int main(int argc, char **argv) SDL_PumpEvents(); } #endif + if (!setting_ss && !hlt_this_time && !(device_status & DEVICE_STATUS_FASTCPU) && ++apm_inst_counter >= APM_INST_DELAY) { + hlt_this_time = 1; + apm_inst_counter = 0; + } + if (hlt_this_time) { struct timespec ts; ts.tv_sec = 0; @@ -1055,7 +1100,8 @@ int main(int argc, char **argv) #ifndef NO_GRAPHICS SDL_Quit(); #endif - return 0; + // I really want to be able to pass an exit code out, but the pre-existing QUITEMU passes garbage. -JH + return (regs16[REG_BX] == 0x1234) ? regs8[REG_AL] : 0; } @@ -1206,6 +1252,54 @@ uint32_t getfreexms() { return lower; } +void callapm() { + char buf[12]; + int h, tmp; + switch (regs8[REG_AH]) { + OPCODE_CHAIN 0: // CPU slow + device_status &= ~DEVICE_STATUS_FASTCPU; + OPCODE 1: // CPU normal + device_status |= DEVICE_STATUS_FASTCPU; + OPCODE 2: // graphics card off + device_status &= ~DEVICE_STATUS_GRAPHICS; + OPCODE 3: // graphics card on + device_status |= DEVICE_STATUS_GRAPHICS; + OPCODE 4: // Get battery status +#ifdef _WIN32 + regs8[REG_BL] = 0xff; +#else + h = open("/sys/class/power_supply/BAT0/status", O_RDONLY); + if (h >= 0 && (0 < read(h, buf, 12))) + regs8[REG_BL] = buf[0] == 'C' ? 3 /* charging */ : 2 /* critical */; + else + regs8[REG_BL] = 0xFF; // Unable + if (h >= 0) close(h); + h = open("/sys/class/power_supply/BAT0/capacity", O_RDONLY); + if (h >= 0 && ((tmp = read(h, buf, 10)) > 0)) + buf[tmp] = 0, regs8[REG_CL] = atoi(buf); + else + regs8[REG_CL] = 0xFF; // Unable + if (h >= 0) close(h); + if (regs8[REG_CL] != 0xFF && regs8[REG_BL] == 2) { + if (regs8[REG_CL] >= 50) regs8[REG_BL] = 0; // High + else if (regs8[REG_CL] >= 10) regs8[REG_BL] = 1; // Low + // If we can't get battery charge and we're not charging it's critical + } +#endif + OPCODE 5: // Get AC status +#ifdef _WIN32 + regs8[REG_BH] = 0xff; +#else + h = open("/sys/class/power_supply/AC/online", O_RDONLY); + if (h >= 0 && (0 < read(h, buf, 2))) + regs8[REG_BH] = buf[0] - 1; // 0 = offline, 1 = online + else + regs8[REG_BH] = 0xFF; // Unable + if (h >= 0) close(h); +#endif + } +} + void callxms() { uint32_t ii, free; diff --git a/Makefile b/Makefile index fe93aa3..1a6ee6b 100644 --- a/Makefile +++ b/Makefile @@ -18,11 +18,14 @@ bios.bin: bios.asm tinyxms.sys: tinyxms.asm nasm -f bin -I ../lmacros/ -I lmacros/ -l tinyxms.lst -o tinyxms.sys tinyxms.asm -8086tiny: 8086tiny.c bios.bin tinyxms.sys +quitemu.com: quitemu.asm + nasm -f bin -o quitemu.com quitemu.asm + +8086tiny: 8086tiny.c bios.bin tinyxms.sys quitemu.com ${CC} 8086tiny.c ${OPTS_SDL} ${OPTS_ALL} -o 8086tiny strip 8086tiny -8086tiny_slowcpu: 8086tiny.c bios.bin tinyxms.sys +8086tiny_slowcpu: 8086tiny.c bios.bin tinyxms.sys quitemu.com ${CC} 8086tiny.c ${OPTS_SDL} ${OPTS_ALL} ${OPTS_SLOWCPU} -o 8086tiny strip 8086tiny @@ -31,4 +34,4 @@ no_graphics: 8086tiny.c bios.bin tinyxms.sys strip 8086tiny clean: - rm -f 8086tiny bios.bin bios.lst tinyxms.sys tinyxms.lst + rm -f 8086tiny bios.bin bios.lst tinyxms.sys tinyxms.lst quitemu.com diff --git a/bios.asm b/bios.asm index e0e92bf..eb60e06 100644 --- a/bios.asm +++ b/bios.asm @@ -55,7 +55,7 @@ main: dw flags_mult ; Table 19: FLAGS multipliers -biosstr db '8086tiny BIOS Revision 1.62!', 0, 0 +biosstr db '8086tiny BIOS Revision 1.70!', 0, 0 bios_entry: @@ -88,11 +88,19 @@ bios_entry: ; Now we can do whatever we want! DL starts off being the boot disk. + cmp byte [cs:boot_state], 0 + jne .warm_early_boot mov [cs:boot_device], dl +.warm_early_boot: + mov [cs:apm_flags], byte 0 - ; Set up Hercules graphics support. We start with the adapter in text mode + push ax - push dx + ; Set CPU to high power + mov ah, 1 + db 0x0f, 0x05 + + ; Set up Hercules graphics support. We start with the adapter in text mode mov dx, 0x3b8 mov al, 0 @@ -111,7 +119,9 @@ bios_entry: mov al, 0x57 ; 0x57 = 87 (* 4) = 348 pixels high (GRAPHICS_Y) out dx, al - pop dx + ; Graphics power on + mov ah, 3 + db 0x0f, 0x05 pop ax @@ -2141,6 +2151,14 @@ int13: je int13_getdisktype cmp ah, 0x16 ; Detect disk change je int13_diskchange + cmp ah, 0x41 + je int13_echeck + cmp ah, 0x42 + je int13_eread_disk + cmp ah, 0x43 + je int13_ewrite_disk + cmp ah, 0x48 + je int13_eparam mov ah, 1 ; Invalid function jmp reach_stack_stc @@ -2204,18 +2222,12 @@ int13: call chs_to_abs - ; Now, SI:BP contains the absolute sector offset of the block. We then multiply by 512 to get the offset into the disk image + ; Now, SI:BP contains the absolute sector offset of the block. - mov ah, 0 - cpu 186 - shl ax, 9 extended_read_disk - shr ax, 9 - cpu 8086 - mov ah, 0x02 ; Put read code back - cmp al, 0 - je rd_error + cmp ah, 0 + jne rd_error ; Read was successful. Now, check if we have read the boot sector. If so, we want to update ; our internal table of sectors/track to match the disk format @@ -2247,14 +2259,11 @@ int13: rd_noerror: - clc - mov ah, 0 ; No error - jmp rd_finish + db 0A8h ; test al, imm8 (NC, skip stc) rd_error: stc - mov ah, 4 ; Sector not found rd_finish: @@ -2323,24 +2332,16 @@ int13: wr_fine: mov ah, 0 - cpu 186 - shl ax, 9 extended_write_disk - shr ax, 9 - cpu 8086 - mov ah, 0x03 ; Put write code back - cmp al, 0 - je wr_error + cmp ah, 0 + jne wr_error - clc - mov ah, 0 ; No error - jmp wr_finish + db 0A8h ; test al, imm8 (NC, skip stc) wr_error: stc - mov ah, 4 ; Sector not found wr_finish: @@ -2451,6 +2452,148 @@ wr_fine: mov ah, 0 ; Disk not changed jmp reach_stack_clc +;****************************************************************** +; INT 13h LBA Extensions - Joshua Hudson 2023 + + int13_echeck: + + cmp dl, 0x80 + jne int13_enofloppy + mov ah, 0x20 + mov bx, 0xAA55 + mov cx, 1 + jmp reach_stack_clc + + int13_enofloppy: ; Special version for int13_echeck: don't set disk status + mov ah, 1 + jmp reach_stack_stc + + int13_eread_disk: + + cmp dl, 0x80 + jne no_int13e + jmp int13_ereadwrite_disk + + int13_ewrite_disk: + + cmp dl, 0x80 + jne no_int13e + ;; Phoenix BIOS had to disable and move write/verify because of messed-up callers + ;; Don't look at low bit of AL to decide write verify or not. We can't implement anyway. + jmp int13_ereadwrite_disk + + int13_eparam: + + cmp dl, 0x80 + jne no_int13e + cmp [si], word 0x1E + jne no_int13e + push ax + xor ax, ax + mov [si + 2], ax + mov [si + 0x06], ax + mov [si + 0x0A], ax + mov [si + 0x0E], ax + mov [si + 0x14], ax + mov [si + 0x16], ax + mov [si + 0x1A], ax + mov [si + 0x1E], ax + mov ax, [cs:hd_max_track] ; Not bothering to detranslate -- there's no ATA device to talk to + mov [si + 0x04], ax + mov ax, [cs:hd_max_head] + mov [si + 0x08], ax + mov ax, [cs:hd_max_sector] + mov [si + 0x0C], ax + mov ax, [cs:hd_secs_lo] + mov [si + 0x10], ax + mov ax, [cs:hd_secs_hi] + mov [si + 0x12], ax + mov [si + 0x18], word 512 + pop ax + mov ah, 0 + jmp int13_eresult + + no_int13e: ; Invalid function argument + mov ah, 1 + int13_eresult: + mov [cs:disk_laststatus], ah + cmp ah, 0 + jne int13_efail + jmp reach_stack_clc + int13_efail: + jmp reach_stack_stc + + int13_ereadwrite_disk: + + cmp [si], byte 0x10 + jne no_int13e + cmp [si + 3], byte 0 + je int13_erangeok + mov ah, 0x0D + jmp int13_eresult + + int13_erangefail: + + mov ah, 0x04 + jmp int13_eresult + + int13_erangeok: + + cmp [si + 12], word 0 + jne int13_erangefail + cmp [si + 14], word 0 + jne int13_erangefail + + push dx + push si + push ax + push bp + push es + push bx + + mov al, [si + 2] + mov es, [si + 6] + mov bx, [si + 4] + mov bp, [si + 8] + mov si, [si + 10] + mov dl, 0 ; Hard disk handle offset + + cmp si, [cs:hd_secs_hi] + jb int13_erangeok2 + ja int13_erangefail2 + cmp bp, [cs:hd_secs_lo] + jae int13_erangefail2 + + int13_erangeok2: + cmp ah, 0x43 + je int13_ewritecall + + extended_read_disk + jmp int13_edecode + + int13_ewritecall: + + extended_write_disk + ;jmp int13_edecode + + int13_edecode: + + mov dh, ah + pop bx + pop es + pop bp + pop ax + mov ah, dh + pop si + pop dx + + jmp int13_eresult + + int13_erangefail2: + + mov ah, 0x04 + jmp int13_edecode + ; ************************* INT 14h - serial port functions int14: @@ -2477,6 +2620,8 @@ int15: ; Here we do not support any of the functions, and just return ; je int15_intercept cmp ah, 0x88 je int15_getextmem + cmp ah, 0x53 + je int15_apm ; Otherwise, function not supported @@ -2510,10 +2655,218 @@ int15_getextmem: db 0Fh, 04h ; call into XMS interface cmc ; if it returned NC, it means error jnc .ret ; no error --> - mov ah, 86h + ; No XMS interface but high memory exists + mov ax, 64 + clc .ret: jmp reach_stack_carry +int15_apmversion: + cmp cx, 0x101 + ja .nope + mov ax, 0x101 + jmp reach_stack_clc +.nope mov ah, 0x0b + jmp reach_stack_stc + +int15_apmbogonstate: + jmp reach_stack_clc + +int15_apm: + cmp al, 0 + je .apmchk + cmp al, 1 + je .real + cmp al, 2 + je .prot16 + cmp al, 3 + je .prot32 + cmp al, 4 + je .realdc + cmp al, 5 + je .cpuidle + cmp al, 6 + je .cpubusy + cmp al, 7 + je .setpower + cmp al, 8 + je .ends + cmp al, 9 + je .ends + cmp al, 10 + je .getbattery + cmp al, 11 + je .getevent + cmp al, 12 + je .getpower + cmp al, 13 + je int15_apmbogonstate + cmp al, 14 + je int15_apmversion + mov ah, 0ah + jmp .fail + +.prot16: ; 16 bit protected mode, but we're an 8086 + mov ah, 6 + jmp .fail +.prot32: ; 32 bit protected mode, lolno + mov ah, 8 +.fail jmp reach_stack_stc +.apmchk: + mov ax, 0x101 + mov bx, 'PM' + mov cx, 4 +.ok jmp reach_stack_clc + +.realdc: ; Real mode APM disconnect + test [cs:apm_flags], byte 1 + jz .notconnected + mov [cs:apm_flags], byte 0 + mov ah, 1 ; Take CPU out of idle + db 0x0f, 0x05 + jmp reach_stack_clc +.notconnected: + mov ah, 0x03 + jmp .fail + +.real: ; Real mode connect -- neither of these do anything interesting + ; Suspend would turn off the mouse; so we can't easily implement a good idle policy + test [cs:apm_flags], byte 1 + jnz .realalready + or bx, bx + jnz .fail + mov [cs:apm_flags], byte 1 + jmp .ok +.realalready: + mov ah, 0x02 + jmp .fail + +.cpuidle: + test [cs:apm_flags], byte 1 + jz .notconnected + push ax + mov ah, 0 + db 0x0f, 0x05 + pop ax + jmp .ok + +.cpubusy: ; Documentation says bios should be able to tell; this bios can't + ; However this bios can only slow on cpuidle so it's all right + test [cs:apm_flags], byte 1 + jz .notconnected + push ax + mov ah, 1 + db 0x0f, 0x05 + pop ax + jmp .ok + +.ends: ; Enable/disable is NOP + test [cs:apm_flags], byte 1 + jz .notconnected + cmp bx, 0xffff + je .ok + jmp .fail + +.setpower: + test [cs:apm_flags], byte 1 + jz .notconnected + cmp cx, 3 + ja .nos + je .cxk + cmp cx, 0 + jne .nxs +.cxk cmp bx, 1 + je .system + cmp bx, 0x100 + je .graphics +.nodevice: + mov ah, 9 + jmp .fail +.nos mov ah, 0x0a + jmp .fail +.nxs mov ah, 0x60 + jmp .fail + +.system cmp cx, 3 + je .poweroff + jmp .ok +.graphics: + push ax + mov ah, 3 + cmp cx, 3 + and [cs:apm_flags], byte 0xfb + je .graphicsoff + mov ah, 2 + or [cs:apm_flags], byte 4 +.graphicsoff: + db 0x0f, 0x05 + pop ax + jmp .ok + +.poweroff: ; Turn off the system + mov bx, bp ; Get error code from caller, if any + mov ax, dx + jmp 0:0 + +.getpower: + cmp bx, 0x100 + je .getgraphics + cmp bx, 1 ; System + jne .nodevice + mov cx, 1 + jmp .ok +.getgraphics: + xor cx, cx + test [cs:apm_flags], byte 4 + jz .getgraphicson + mov cl, 3 +.getgraphicson: + jmp .ok + +.getbattery: + cmp bx, 1 + ja .nodevice ; asks for specific battery; but we are APM 1.1 + push ax + ; This call always succeeds + mov ah, 4 + db 0x0f, 0x05 + mov ah, 5 + db 0x0f, 0x05 + pop ax + jmp reach_stack_clc + +.getevent: + ; There's only one event: battery + test [cs:apm_flags], byte 1 + jz .notconnected + push bp + mov bp, sp + push ax + push bx + push cx + mov ah, 4 + db 0x0f, 0x05 + cmp bl, 2 + jne .gennocrit + test [cs:apm_flags], byte 2 + jz .gencritalready + or [cs:apm_flags], byte 2 + ; I hate this specification. What is labelled as low battery notification is critical battery notification and + ; what is labelled as critical battery notification is critical batter after waking from unscheduled suspend. -JH + mov [bp - 4], word 5 ; AX = battery notification + clc + jmp .geexit +.gennocrit: + and [cs:apm_flags], byte 0xfd +.gencritalready: + mov [bp - 1], byte 80h ; AH = no event + stc +.geexit: + pop cx + pop bx + pop ax + pop bp + jmp reach_stack_carry ; ************************* INT 16h handler - keyboard @@ -3880,6 +4233,7 @@ int_table dw int0 dw int1d dw 0xf000 dw int1e + dw 0xf000 itbl_size equ $-int_table @@ -3916,6 +4270,9 @@ crt_curpos_y_last db 0 last_int8_msec dw 0 last_key_sdl db 0 +; APM data +apm_flags db 0 + ; Now follow the tables for instruction decode helping align 8 @@ -4087,4 +4444,4 @@ tm_msec equ $+36 mem_top: jmp 0F000h:100h - db '03/08/14', 0, 0xfe, 0 + db '11/26/23', 0, 0xfe, 0 diff --git a/quitemu.asm b/quitemu.asm new file mode 100644 index 0000000..ddcd7df --- /dev/null +++ b/quitemu.asm @@ -0,0 +1,50 @@ +CPU 8086 + +BITS 16 + +ORG 0x100 + + mov si, 0x80 + lodsb + mov bl, al + mov bh, 0 + add bx, si + mov cl, 10 + xor dx, dx +lop: + cmp si, bx + je go + lodsb + cmp al, ' ' + je lop + cmp al, 9 + je lop + sub al, '0' + jb help + cmp al, 9 + ja help + xchg ax, dx + mul cl + add al, dl + xchg ax, dx + jmp lop +help: + mov dx, msg + mov ah, 9 + int 21h + ret +go: + mov bp, 0x1234 ; Tell emulator to use AL as exit code + xor bx, bx + mov ax, 5304h + int 15h + mov ax, 5301h + int 15h + mov ax, 5307h + mov cx, 3 + inc bx + int 15h + mov ax, 4C01h ; Power off failed + int 21h + ret ; DOS 1.0 +msg db 'Quits 8086tiny emulator', 13, 10, 'Usage: QUITEMU [exit code]', 13, 10, '$' diff --git a/runme b/runme index 702d6d0..3d798a5 100755 --- a/runme +++ b/runme @@ -7,4 +7,6 @@ then else ./8086tiny bios.bin fd.img fi +Q=$? stty cooked echo +exit $Q