fpc/rtl/msdos/system.pp
nickysn 4f573b50ad * access the Default8087CW global variable directly from within inline asm,
instead of copying it to a local var first, since the concern about PIC code
  (this asm code started out as a copy of the i386 rtl code) doesn't exist on
  the i8086, since there's no such thing as PIC code on the i8086

git-svn-id: trunk@41311 -
2019-02-12 16:31:16 +00:00

719 lines
18 KiB
ObjectPascal

unit System;
interface
{ The heap for MSDOS is implemented
in tinyheap.inc include file,
but it uses default SysGetMem names }
{$define HAS_MEMORYMANAGER}
{ define TEST_FPU_INT10 to force keeping local int10,
for testing purpose only }
{$DEFINE FPC_INCLUDE_SOFTWARE_MUL}
{$DEFINE FPC_INCLUDE_SOFTWARE_MOD_DIV}
{$DEFINE FPC_USE_SMALL_DEFAULTSTACKSIZE}
{ To avoid warnings in thread.inc code,
but value must be really given after
systemh.inc is included otherwise the
$mode switch is not effective }
{ Use Ansi Char for files }
{$define FPC_ANSI_TEXTFILEREC}
{$define FPC_STDOUT_TRUE_ALIAS}
{$ifdef NO_WIDESTRINGS}
{ Do NOT use wide Char for files }
{$undef FPC_HAS_FEATURE_WIDESTRINGS}
{$endif NO_WIDESTRINGS}
{$I systemh.inc}
{$I tnyheaph.inc}
{$I portsh.inc}
const
LineEnding = #13#10;
{ LFNSupport is a variable here, defined below!!! }
DirectorySeparator = '\';
DriveSeparator = ':';
ExtensionSeparator = '.';
PathSeparator = ';';
AllowDirectorySeparators : set of char = ['\','/'];
AllowDriveSeparators : set of char = [':'];
{ FileNameCaseSensitive and FileNameCasePreserving are defined separately below!!! }
maxExitCode = 255;
MaxPathLen = 256;
const
{ Default filehandles }
UnusedHandle = $ffff;{ instead of -1, as it is a word value}
StdInputHandle = 0;
StdOutputHandle = 1;
StdErrorHandle = 2;
FileNameCaseSensitive : boolean = false;
FileNameCasePreserving: boolean = false;
CtrlZMarksEOF: boolean = true; (* #26 is considered as end of file *)
sLineBreak = LineEnding;
DefaultTextLineBreakStyle : TTextLineBreakStyle = tlbsCRLF;
{ Default memory segments (Tp7 compatibility) }
seg0040: Word = $0040;
segA000: Word = $A000;
segB000: Word = $B000;
segB800: Word = $B800;
{ The value that needs to be added to the segment to move the pointer by
64K bytes (BP7 compatibility) }
SelectorInc: Word = $1000;
var
{ Mem[] support }
mem : array[0..$7fff-1] of byte absolute $0:$0;
memw : array[0..($7fff div sizeof(word))-1] of word absolute $0:$0;
meml : array[0..($7fff div sizeof(longint))-1] of longint absolute $0:$0;
{ C-compatible arguments and environment }
argc:smallint; //!! public name 'operatingsystem_parameter_argc';
argv:PPchar; //!! public name 'operatingsystem_parameter_argv';
{ The DOS Program Segment Prefix segment (TP7 compatibility) }
PrefixSeg:Word;public name '__fpc_PrefixSeg';
SaveInt00: FarPointer;public name '__SaveInt00';
SaveInt10: FarPointer;public name '__SaveInt10';
SaveInt75: FarPointer;public name '__SaveInt75';
fpu_status: word;public name '__fpu_status';
AllFilesMask: string [3];
{$ifndef RTLLITE}
{ System info }
LFNSupport : boolean;
{$ELSE RTLLITE}
const
LFNSupport = false;
{$endif RTLLITE}
implementation
procedure DebugWrite(const S: string); forward;
procedure DebugWriteLn(const S: string); forward;
const
fCarry = 1;
{ used for an offset fixup for accessing the proc parameters in asm routines
that use nostackframe. We can't use the parameter name directly, because
i8086 doesn't support sp relative addressing. }
{$ifdef FPC_X86_CODE_FAR}
extra_param_offset = 2;
{$else FPC_X86_CODE_FAR}
extra_param_offset = 0;
{$endif FPC_X86_CODE_FAR}
{$if defined(FPC_X86_DATA_FAR) or defined(FPC_X86_DATA_HUGE)}
extra_data_offset = 2;
{$else}
extra_data_offset = 0;
{$endif}
type
PFarByte = ^Byte;far;
PFarChar = ^Char;far;
PFarWord = ^Word;far;
PPFarChar = ^PFarChar;
var
__stktop : pointer;public name '__stktop';
__stkbottom : pointer;public name '__stkbottom';
__nearheap_start: pointer;public name '__nearheap_start';
__nearheap_end: pointer;public name '__nearheap_end';
dos_version:Word;public name 'dos_version';
dos_env_count:smallint;public name '__dos_env_count';
dos_argv0 : PFarChar;public name '__fpc_dos_argv0';
{$I registers.inc}
procedure Intr(IntNo: Byte; var Regs: Registers); external name 'FPC_INTR';
procedure MsDos(var Regs: Registers); external name 'FPC_MSDOS';
{ invokes int 21h with the carry flag set on entry; used for the LFN functions
to ensure that the carry flag is set on exit on older DOS versions which don't
support them }
procedure MsDos_Carry(var Regs: Registers); external name 'FPC_MSDOS_CARRY';
procedure InstallInterruptHandlers; external name 'FPC_INSTALL_INTERRUPT_HANDLERS';
procedure RestoreInterruptHandlers; external name 'FPC_RESTORE_INTERRUPT_HANDLERS';
function CheckNullArea: Boolean; external name 'FPC_CHECK_NULLAREA';
var
test_fpu_jmpbuf : jmp_buf;
Procedure InterceptInvalidInstruction;
begin
longjmp(test_fpu_jmpbuf, 1);
end;
{ Use msdos int21 set/get Interrupt address
to check if coprocessor is present }
{$define FPC_SYSTEM_HAS_SYSINITFPU}
Procedure SysInitFPU;
const
CR0_NE = $20;
CR0_NOT_NE = $FFFF - CR0_NE;
var
prevInt06 : FarPointer;
_newcr0_lw : word;
restore_old_int10 : boolean;
begin
restore_old_int10:=false;
asm
fninit
fldcw Default8087CW
fwait
end;
if Test8087 < 3 then { i8087/i80287 do not have "native" exception mode (CR0:NE) }
begin
restore_old_int10:=true;
end
else
begin
asm
push es
push ds
{ Get previous interrupt 06 handler }
mov ax, $3506
int $21
mov word [prevInt06],bx
mov dx,es
mov word [prevInt06+2],dx
{ Install local interrupt 06 handler }
{$ifdef FPC_MM_TINY}
{ Do not use SEG here, as this introduces a relocation that
is incompatible with COM executable generation }
mov dx, cs
{$else FPC_MM_TINY}
mov dx, SEG InterceptInvalidInstruction
{$endif FPC_MM_TINY}
mov ds, dx
mov dx, Offset InterceptInvalidInstruction
mov ax, $2506
int $21
pop ds
pop es
end;
if setjmp(test_fpu_jmpbuf)=0 then
begin
asm
db $0f, $20, $c0 { mov eax,cr0 }
{ Reset CR0 Numeric Error bit,
to trigger IRQ13 - interrupt $75,
and thus avoid the generation of a $10 trap
which iterferes with video interrupt handler }
and ax,CR0_NOT_NE
db $0f, $22, $c0 { mov cr0,eax }
end;
//writeln(stderr,'Change of cr0 succeeded');
// Test that NE bit is indeed reset
asm
db $0f, $20, $c0 { mov eax,cr0 }
mov _newcr0_lw, ax
end;
if (_newcr0_lw and CR0_NE) = 0 then
restore_old_int10:=true;
end
else
begin
//writeln(stderr,'Change of cr0 failed');
end;
{ Restore previous interrupt 06 handler }
asm
push ds
mov ax, $2506
lds dx,[prevInt06]
int $21
pop ds
end;
end;
{ Special handler of interrupt $10
not needed anymore
Restore previous interrupt $10 handler }
{$ifndef TEST_FPU_INT10}
if restore_old_int10 then
asm
push ds
mov ax, $2510
lds dx,[SaveInt10]
int $21
pop ds
end;
{$endif ndef TEST_FPU_INT10}
end;
{$I system.inc}
{$I tinyheap.inc}
{$I ports.inc}
procedure DebugWrite(const S: string);
begin
asm
{$if defined(FPC_X86_DATA_FAR) or defined(FPC_X86_DATA_HUGE)}
push ds
lds si, S
{$else}
mov si, S
{$endif}
{$ifdef FPC_ENABLED_CLD}
cld
{$endif FPC_ENABLED_CLD}
lodsb
mov cl, al
xor ch, ch
jcxz @@zero_length
mov ah, 2
@@1:
lodsb
mov dl, al
int 21h
loop @@1
@@zero_length:
{$if defined(FPC_X86_DATA_FAR) or defined(FPC_X86_DATA_HUGE)}
pop ds
{$endif}
end ['ax','bx','cx','dx','si','di'];
end;
procedure DebugWriteLn(const S: string);
begin
DebugWrite(S);
DebugWrite(#13#10);
end;
{*****************************************************************************
ParamStr/Randomize
*****************************************************************************}
var
internal_envp : PPFarChar = nil;
procedure setup_environment;
var
env_count : smallint;
cp, dos_env: PFarChar;
begin
env_count:=0;
dos_env:=Ptr(MemW[PrefixSeg:$2C], 0);
cp:=dos_env;
while cp^<>#0 do
begin
inc(env_count);
while (cp^ <> #0) do
inc(cp); { skip to NUL }
inc(cp); { skip to next character }
end;
internal_envp := getmem((env_count+1) * sizeof(PFarChar));
cp:=dos_env;
env_count:=0;
while cp^<>#0 do
begin
internal_envp[env_count] := cp;
inc(env_count);
while (cp^ <> #0) do
inc(cp); { skip to NUL }
inc(cp); { skip to next character }
end;
internal_envp[env_count]:=nil;
dos_env_count := env_count;
if dos_version >= $300 then
begin
if cp=dos_env then
inc(cp);
inc(cp, 3);
dos_argv0 := cp;
end
else
dos_argv0 := nil;
end;
function envp:PPFarChar;public name '__fpc_envp';
begin
if not assigned(internal_envp) then
setup_environment;
envp:=internal_envp;
end;
procedure setup_arguments;
var
I: SmallInt;
pc: PChar;
pfc: PFarChar;
quote: Char;
count: SmallInt;
arglen, argv0len: SmallInt;
argblock: PChar;
arg: PChar;
doscmd : string[129]; { Dos commandline copied from PSP, max is 128 chars +1 for terminating zero }
begin
{ force environment to be setup so dos_argv0 is loaded }
envp;
{ load commandline from psp }
SetLength(doscmd, Mem[PrefixSeg:$80]);
for I := 1 to length(doscmd) do
doscmd[I] := Chr(Mem[PrefixSeg:$80+I]);
doscmd[length(doscmd)+1]:=#0;
{$IfDef SYSTEM_DEBUG_STARTUP}
Writeln(stderr,'Dos command line is #',doscmd,'# size = ',length(doscmd));
{$EndIf }
{ find argv0len }
argv0len:=0;
if dos_argv0<>nil then
begin
pfc:=dos_argv0;
while pfc^<>#0 do
begin
Inc(argv0len);
Inc(pfc);
end;
end;
{ parse dos commandline }
pc:=@doscmd[1];
count:=1;
{ calc total arguments length and count }
arglen:=argv0len+1;
while pc^<>#0 do
begin
{ skip leading spaces }
while pc^ in [#1..#32] do
inc(pc);
if pc^=#0 then
break;
{ calc argument length }
quote:=' ';
while (pc^<>#0) do
begin
case pc^ of
#1..#32 :
begin
if quote<>' ' then
inc(arglen)
else
break;
end;
'"' :
begin
if quote<>'''' then
begin
if pchar(pc+1)^<>'"' then
begin
if quote='"' then
quote:=' '
else
quote:='"';
end
else
inc(pc);
end
else
inc(arglen);
end;
'''' :
begin
if quote<>'"' then
begin
if pchar(pc+1)^<>'''' then
begin
if quote='''' then
quote:=' '
else
quote:='''';
end
else
inc(pc);
end
else
inc(arglen);
end;
else
inc(arglen);
end;
inc(pc);
end;
inc(arglen); { for the null terminator }
inc(count);
end;
{ set argc and allocate argv }
argc:=count;
argv:=AllocMem((count+1)*SizeOf(PChar));
{ allocate a single memory block for all arguments }
argblock:=GetMem(arglen);
{ create argv[0] }
argv[0]:=argblock;
arg:=argblock;
if dos_argv0<>nil then
begin
pfc:=dos_argv0;
while pfc^<>#0 do
begin
arg^:=pfc^;
Inc(arg);
Inc(pfc);
end;
end;
arg^:=#0;
Inc(arg);
pc:=@doscmd[1];
count:=1;
while pc^<>#0 do
begin
{ skip leading spaces }
while pc^ in [#1..#32] do
inc(pc);
if pc^=#0 then
break;
{ copy argument }
argv[count]:=arg;
quote:=' ';
while (pc^<>#0) do
begin
case pc^ of
#1..#32 :
begin
if quote<>' ' then
begin
arg^:=pc^;
inc(arg);
end
else
break;
end;
'"' :
begin
if quote<>'''' then
begin
if pchar(pc+1)^<>'"' then
begin
if quote='"' then
quote:=' '
else
quote:='"';
end
else
inc(pc);
end
else
begin
arg^:=pc^;
inc(arg);
end;
end;
'''' :
begin
if quote<>'"' then
begin
if pchar(pc+1)^<>'''' then
begin
if quote='''' then
quote:=' '
else
quote:='''';
end
else
inc(pc);
end
else
begin
arg^:=pc^;
inc(arg);
end;
end;
else
begin
arg^:=pc^;
inc(arg);
end;
end;
inc(pc);
end;
arg^:=#0;
Inc(arg);
{$IfDef SYSTEM_DEBUG_STARTUP}
Writeln(stderr,'dos arg ',count,' #',strlen(argv[count]),'#',argv[count],'#');
{$EndIf SYSTEM_DEBUG_STARTUP}
inc(count);
end;
end;
function paramcount : longint;
begin
if argv=nil then
setup_arguments;
paramcount := argc - 1;
end;
function paramstr(l : longint) : string;
begin
if argv=nil then
setup_arguments;
if (l>=0) and (l+1<=argc) then
paramstr:=strpas(argv[l])
else
paramstr:='';
end;
procedure randomize;
var
hl : longint;
regs : Registers;
begin
regs.AH:=$2C;
MsDos(regs);
hl:=regs.DX;
randseed:=hl*$10000+ regs.CX;
end;
{*****************************************************************************
System Dependent Exit code
*****************************************************************************}
procedure system_exit;
var
h : byte;
begin
RestoreInterruptHandlers;
for h:=0 to max_files-1 do
if openfiles[h] then
begin
{$ifdef SYSTEMDEBUG}
writeln(stderr,'file ',h,' "',opennames[h],'" not closed at exit');
{$endif SYSTEMDEBUG}
if h>=5 then
do_close(h);
end;
{$ifndef FPC_MM_TINY}
if not CheckNullArea then
writeln(stderr, 'Nil pointer assignment');
{$endif FPC_MM_TINY}
asm
mov al, byte [exitcode]
mov ah, 4Ch
int 21h
end;
end;
{*****************************************************************************
SystemUnit Initialization
*****************************************************************************}
procedure InitDosHeap;
type
{$if defined(FPC_X86_DATA_FAR) or defined(FPC_X86_DATA_HUGE)}
TPointerArithmeticType = HugePointer;
{$else}
TPointerArithmeticType = Pointer;
{$endif}
begin
RegisterTinyHeapBlock_Simple_Prealigned(__nearheap_start, TPointerArithmeticType(__nearheap_end) - TPointerArithmeticType(__nearheap_start));
end;
function CheckLFN:boolean;
var
regs : Registers;
RootName : pchar;
buf : array [0..31] of char;
begin
{ Check LFN API on drive c:\ }
RootName:='C:\';
{ Call 'Get Volume Information' ($71A0) }
regs.AX:=$71a0;
regs.ES:=Seg(buf);
regs.DI:=Ofs(buf);
regs.CX:=32;
regs.DS:=Seg(RootName^);
regs.DX:=Ofs(RootName^);
MsDos_Carry(regs);
{ If carryflag=0 and LFN API bit in ebx is set then use Long file names }
CheckLFN:=(regs.Flags and fCarry=0) and (regs.BX and $4000=$4000);
end;
procedure SysInitStdIO;
begin
OpenStdIO(Input,fmInput,StdInputHandle);
OpenStdIO(Output,fmOutput,StdOutputHandle);
OpenStdIO(ErrOutput,fmOutput,StdErrorHandle);
{$ifndef FPC_STDOUT_TRUE_ALIAS}
OpenStdIO(StdOut,fmOutput,StdOutputHandle);
OpenStdIO(StdErr,fmOutput,StdErrorHandle);
{$endif FPC_STDOUT_TRUE_ALIAS}
end;
function GetProcessID: SizeUInt;
begin
GetProcessID := PrefixSeg;
end;
function CheckInitialStkLen(stklen : SizeUInt) : SizeUInt;
begin
result := stklen;
end;
begin
StackBottom := __stkbottom;
StackLength := __stktop - __stkbottom;
InstallInterruptHandlers;
DetectFPU;
if Test8087>0 then
SysInitFPU;
{ To be set if this is a GUI or console application }
IsConsole := TRUE;
{$ifdef FPC_HAS_FEATURE_DYNLIBS}
{ If dynlibs feature is disabled,
IsLibrary is a constant, which can thus not be set to a value }
{ To be set if this is a library and not a program }
IsLibrary := FALSE;
{$endif def FPC_HAS_FEATURE_DYNLIBS}
{ Setup heap }
InitDosHeap;
SysInitExceptions;
{$ifdef FPC_HAS_FEATURE_UNICODESTRINGS}
initunicodestringmanager;
{$endif def FPC_HAS_FEATURE_UNICODESTRINGS}
{ Setup stdin, stdout and stderr }
SysInitStdIO;
{ Setup environment and arguments }
{ Done on request only Setup_Environment; }
{ Done on request only Setup_Arguments; }
{$ifndef RTLLITE}
{ Use LFNSupport LFN }
LFNSupport:=CheckLFN;
if LFNSupport then
begin
FileNameCasePreserving:=true;
AllFilesMask := '*';
end
else
{$endif ndef RTLLITE}
AllFilesMask := '*.*';
{ Reset IO Error }
InOutRes:=0;
{$ifdef FPC_HAS_FEATURE_THREADING}
InitSystemThreads;
{$endif}
end.