| /* |
| * BRLTTY - A background process providing access to the console screen (when in |
| * text mode) for a blind person using a refreshable braille display. |
| * |
| * Copyright (C) 1995-2023 by The BRLTTY Developers. |
| * |
| * BRLTTY comes with ABSOLUTELY NO WARRANTY. |
| * |
| * This is free software, placed under the terms of the |
| * GNU Lesser General Public License, as published by the Free Software |
| * Foundation; either version 2.1 of the License, or (at your option) any |
| * later version. Please see the file LICENSE-LGPL for details. |
| * |
| * Web Page: http://brltty.app/ |
| * |
| * This software is maintained by Dave Mielke <dave@mielke.cc>. |
| */ |
| |
| #include "prologue.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #include <setjmp.h> |
| #include <dpmi.h> |
| #include <pc.h> |
| #include <dos.h> |
| #include <go32.h> |
| #include <crt0.h> |
| #include <sys/farptr.h> |
| |
| #include "log.h" |
| #include "system.h" |
| #include "system_msdos.h" |
| |
| int _crt0_startup_flags = _CRT0_FLAG_LOCK_MEMORY; |
| |
| /* reduce image */ |
| int _stklen = 0X2000; |
| |
| void __crt0_load_environment_file(char *_app_name) { return; } |
| char **__crt0_glob_function(char *_arg) { return 0; } |
| |
| static void tsrExit (void) NORETURN; |
| |
| /* disable this bit of magic as it causes a page fault on exit */ |
| #if 0 |
| /* Start undocumented way to make exception handling disappear (v2.03) */ |
| short __djgpp_ds_alias; |
| void __djgpp_exception_processor(void) { return; } |
| void __djgpp_exception_setup(void) { return; } |
| void __djgpp_exception_toggle(void) { return; } |
| int __djgpp_set_ctrl_c(int enable) { return 0; } |
| void __maybe_fix_w2k_ntvdm_bug(void) { } |
| void abort(void) { tsrExit(); } |
| void _exit(int status) { tsrExit(); } |
| int raise(int sig) { return 0; } |
| void *signal(int signum, void*handler) { return NULL; } |
| /* End undocumented way to make exception handling disappear */ |
| #endif |
| |
| #define TIMER_INTERRUPT 0X08 |
| #define DOS_INTERRUPT 0X21 |
| #define IDLE_INTERRUPT 0X28 |
| |
| /* For saving interrupt and main contexts */ |
| |
| typedef char FpuState[(7+20)*8]; |
| typedef struct { |
| unsigned short segment; |
| unsigned short offset; |
| } DiskTransferAddress; |
| |
| typedef struct { |
| FpuState fpu; |
| DiskTransferAddress dta; |
| unsigned short psp; |
| } State; |
| |
| static State mainState; |
| static State interruptState; |
| |
| static jmp_buf mainContext; |
| static jmp_buf interruptContext; |
| |
| static int isBackgrounded = 0; /* whether we really TSR */ |
| |
| static unsigned long inDosFlagPointer; |
| static unsigned long criticalOffset; |
| |
| /* prevent reentrancy */ |
| static volatile int inTimerInterrupt = 0; |
| static volatile int inIdleInterrupt = 0; |
| |
| static inline int |
| inInterrupt (void) { |
| return inTimerInterrupt || inIdleInterrupt; |
| } |
| |
| static volatile unsigned long elapsedTickCount; |
| static volatile unsigned long elapsedTickIncrement; |
| |
| static __dpmi_regs idleRegisters; |
| |
| static _go32_dpmi_seginfo origTimerSeginfo, timerSeginfo; |
| static _go32_dpmi_seginfo origIdleSeginfo, idleSeginfo; |
| |
| /* handle Program Segment Prefix switch for proper file descriptor table */ |
| static unsigned short |
| getProgramSegmentPrefix (void) { |
| __dpmi_regs r; |
| |
| r.h.ah = 0X51; |
| __dpmi_int(DOS_INTERRUPT, &r); |
| return r.x.bx; |
| } |
| |
| static void |
| setProgramSegmentPrefix (unsigned short segment) { |
| __dpmi_regs r; |
| |
| r.h.ah = 0X50; |
| r.x.bx = segment; |
| __dpmi_int(DOS_INTERRUPT, &r); |
| } |
| |
| /* handle Disk Transfer Address switch since djgpp uses it for FindFirst/FindNext */ |
| static void |
| getDiskTransferAddress (DiskTransferAddress *dta) { |
| __dpmi_regs r; |
| |
| r.h.ah = 0X2F; |
| __dpmi_int(DOS_INTERRUPT, &r); |
| |
| dta->segment = r.x.es; |
| dta->offset = r.x.bx; |
| } |
| |
| static void |
| setDiskTransferAddress (const DiskTransferAddress *dta) { |
| __dpmi_regs r; |
| |
| r.h.ah = 0X1A; |
| r.x.ds = dta->segment; |
| r.x.dx = dta->offset; |
| __dpmi_int(DOS_INTERRUPT, &r); |
| } |
| |
| unsigned short |
| msdosGetCodePage (void) { |
| __dpmi_regs r; |
| |
| r.h.ah = 0X66; |
| r.h.al = 0X01; |
| __dpmi_int(DOS_INTERRUPT, &r); |
| |
| return r.x.bx; |
| } |
| |
| /* Handle FPU state switch */ |
| #define saveFpuState(p) asm volatile("fnsave (%0); fwait"::"r"(p):"memory") |
| #define restoreFpuState(p) asm volatile("frstor (%0)"::"r"(p)) |
| |
| static void |
| saveState (State *state) { |
| saveFpuState(&state->fpu); |
| getDiskTransferAddress(&state->dta); |
| state->psp = getProgramSegmentPrefix(); |
| } |
| |
| static void |
| restoreState (const State *state) { |
| restoreFpuState(&state->fpu); |
| setDiskTransferAddress(&state->dta); |
| setProgramSegmentPrefix(state->psp); |
| } |
| |
| static unsigned short |
| getTicksTillNextTimerInterrupt (void) { |
| unsigned char clo, chi; |
| |
| outportb(0X43, 0XD2); |
| clo = inportb(0X40); |
| chi = inportb(0X40); |
| |
| return (chi << 8) | clo; |
| } |
| |
| /* Timer interrupt handler */ |
| static void |
| timerInterruptHandler (void) { |
| elapsedTickCount += elapsedTickIncrement; |
| elapsedTickIncrement = getTicksTillNextTimerInterrupt(); |
| |
| if (!inInterrupt()) { |
| inTimerInterrupt = 1; |
| if (!setjmp(interruptContext)) longjmp(mainContext, 1); |
| inTimerInterrupt = 0; |
| } |
| } |
| |
| /* Idle interrupt handler */ |
| static void |
| idleInterruptHandler (_go32_dpmi_registers *r) { |
| if (!inInterrupt()) { |
| inIdleInterrupt = 1; |
| if (!setjmp(interruptContext)) longjmp(mainContext, 1); |
| inIdleInterrupt = 0; |
| } |
| |
| r->x.cs = origIdleSeginfo.rm_segment; |
| r->x.ip = origIdleSeginfo.rm_offset; |
| _go32_dpmi_simulate_fcall_iret(r); |
| } |
| |
| /* Try to restore interrupt handler */ |
| static int |
| restore (int vector, _go32_dpmi_seginfo *seginfo, _go32_dpmi_seginfo *orig_seginfo) { |
| _go32_dpmi_seginfo cur_seginfo; |
| |
| _go32_dpmi_get_protected_mode_interrupt_vector(vector, &cur_seginfo); |
| |
| if ((cur_seginfo.pm_selector != seginfo->pm_selector) || |
| (cur_seginfo.pm_offset != seginfo->pm_offset)) { |
| return 1; |
| } |
| |
| _go32_dpmi_set_protected_mode_interrupt_vector(vector, orig_seginfo); |
| return 0; |
| } |
| |
| /* TSR exit: trying to free as many resources as possible */ |
| static void |
| tsrExit (void) { |
| if (isBackgrounded) { |
| unsigned long pspAddress = _go32_info_block.linear_address_of_original_psp; |
| |
| if (restore(TIMER_INTERRUPT, &timerSeginfo, &origTimerSeginfo) + |
| restore(IDLE_INTERRUPT, &idleSeginfo, &origIdleSeginfo)) { |
| /* failed, hang */ |
| setjmp(mainContext); |
| longjmp(interruptContext, 1); |
| } |
| |
| { |
| __dpmi_regs r; |
| |
| /* free environment */ |
| r.x.es = _farpeekw(_dos_ds, pspAddress+0X2C); |
| r.x.ax = 0X4900; |
| __dpmi_int(DOS_INTERRUPT, &r); |
| |
| /* free Program Segment Prefix */ |
| r.x.es = pspAddress / 0X10; |
| r.x.ax = 0X4900; |
| __dpmi_int(DOS_INTERRUPT, &r); |
| } |
| |
| /* and return */ |
| longjmp(interruptContext, 1); |
| |
| /* TODO: free protected mode memory */ |
| } |
| } |
| |
| /* go to background: TSR */ |
| void |
| msdosBackground (void) { |
| __djgpp_set_ctrl_c(0); |
| saveState(&mainState); |
| |
| if (!setjmp(mainContext)) { |
| __dpmi_regs regs; |
| |
| /* set a chained Protected Mode Timer IRQ handler */ |
| timerSeginfo.pm_selector = _my_cs(); |
| timerSeginfo.pm_offset = (unsigned long)&timerInterruptHandler; |
| _go32_dpmi_get_protected_mode_interrupt_vector(TIMER_INTERRUPT, &origTimerSeginfo); |
| _go32_dpmi_chain_protected_mode_interrupt_vector(TIMER_INTERRUPT, &timerSeginfo); |
| |
| /* set a real mode DOS Idle handler which calls back our Idle handler */ |
| idleSeginfo.pm_selector = _my_cs(); |
| idleSeginfo.pm_offset = (unsigned long)&idleInterruptHandler; |
| memset(&idleRegisters, 0, sizeof(idleRegisters)); |
| _go32_dpmi_get_real_mode_interrupt_vector(IDLE_INTERRUPT, &origIdleSeginfo); |
| _go32_dpmi_allocate_real_mode_callback_iret(&idleSeginfo, &idleRegisters); |
| _go32_dpmi_set_real_mode_interrupt_vector(IDLE_INTERRUPT, &idleSeginfo); |
| |
| /* Get InDos and Critical flags addresses */ |
| regs.h.ah = 0X34; |
| __dpmi_int(DOS_INTERRUPT, ®s); |
| inDosFlagPointer = msdosMakeAddress(regs.x.es, regs.x.bx); |
| |
| regs.x.ax = 0X5D06; |
| __dpmi_int(DOS_INTERRUPT, ®s); |
| criticalOffset = msdosMakeAddress(regs.x.ds, regs.x.si); |
| |
| /* We are ready */ |
| isBackgrounded = 1; |
| |
| regs.x.ax = 0X3100; |
| msdosBreakAddress(0X100/*psp*/ + _go32_info_block.size_of_transfer_buffer, 0, |
| ®s.x.dx, NULL); |
| __dpmi_int(DOS_INTERRUPT, ®s); |
| |
| /* shouldn't be reached */ |
| logMessage(LOG_ERR, "TSR installation failed"); |
| isBackgrounded = 0; |
| } |
| |
| saveState(&interruptState); |
| restoreState(&mainState); |
| } |
| |
| unsigned long |
| msdosUSleep (unsigned long microseconds) { |
| unsigned long ticks; |
| |
| if (!isBackgrounded) { |
| usleep(microseconds); |
| return microseconds; |
| } |
| |
| saveState(&mainState); |
| restoreState(&interruptState); |
| |
| /* clock ticks to wait */ |
| ticks = (microseconds * MSDOS_PIT_FREQUENCY) / UINT64_C(1000000); |
| |
| /* we're starting in the middle of a timer period */ |
| { |
| int wasEnabled = disable(); |
| |
| elapsedTickIncrement = getTicksTillNextTimerInterrupt(); |
| elapsedTickCount = 0; |
| |
| if (wasEnabled) enable(); |
| } |
| |
| while (elapsedTickCount < ticks) { |
| /* wait for next interrupt */ |
| if (!setjmp(mainContext)) longjmp(interruptContext, 1); |
| /* interrupt returned */ |
| } |
| |
| /* wait for Dos to be free */ |
| setjmp(mainContext); |
| |
| /* critical sections of DOS are never reentrant */ |
| if (_farpeekb(_dos_ds, criticalOffset) |
| /* DOS is busy but not idle */ |
| || (!inIdleInterrupt && _farpeekb(_dos_ds, inDosFlagPointer))) |
| longjmp(interruptContext, 1); |
| |
| saveState(&interruptState); |
| restoreState(&mainState); |
| |
| return (elapsedTickCount * UINT64_C(1000000)) / MSDOS_PIT_FREQUENCY; |
| } |
| |
| int |
| vsnprintf (char *str, size_t size, const char *format, va_list ap) { |
| size_t alloc = 1024; |
| char *buf; |
| int ret; |
| |
| if (alloc < size) alloc = size; |
| buf = alloca(alloc); |
| ret = vsprintf(buf, format, ap); |
| if (size > (ret + 1)) size = ret + 1; |
| memcpy(str, buf, size); |
| return ret; |
| } |
| |
| int |
| snprintf (char *str, size_t size, const char *format, ...) { |
| va_list argp; |
| int ret; |
| |
| va_start(argp, format); |
| ret = vsnprintf(str, size, format, argp); |
| va_end(argp); |
| |
| return ret; |
| } |
| |
| void |
| initializeSystemObject (void) { |
| } |