>Newsgroups: comp.lang.perl
>From: [email protected] ("John Dallman")
>Subject: Re: Perl for DOS needs int86x()  (was Serial I/O & port setup :DOS:
>Message-ID: <[email protected]>
>Organization: Compulink Information eXchange
>References: <[email protected]>
>Date: Mon, 19 Sep 1994 19:37:06 GMT
>X-News-Software: Ameol from CIX
>Lines: 156

OK, I give up - here it is. Warning: lots of DOS stuff in here; slight
revisions for clarity over the version I mailed to [email protected]
(Darryl Okahata)

The MS-DOS/BIOS programming interface is complex and woolly. All of it is
addressed via 8086 interrupts, with at least some parameters passed in
registers - but after that it gets complicated. A primary requirement for
a workable scheme is that it should not require the Perl implementation
to know all about all the MS-DOS calls - in any case, addressing
extensions to DOS is one major requirement for this job.  Rather than add
keywords to Perl, the best idea seems to be to make use of
syscall(), since that is implementation-defined in any case,

The manpage has:

> syscall(LIST)
> > syscall LIST
> Calls the system call specified as the first element > of the list,
passing the remaining elements as arguments > to the system call.  If
unimplemented, produces a fatal > error.  > > The arguments are
interpreted as follows: if a given > argument is numeric, the argument is
passed as an int.  > If not, the pointer to the string value is passed.
> You are responsible to make sure a string is pre-extended > long
enough to receive any result that might be written > into a string.  If
your integer arguments are not > literals and have never been interpreted
in a numeric > context, you may need to add 0 to them to force > them to
look like numbers.

Clearly Perl's syscall() handler can deal with the string and numeric
parameter stuff; we need to design a C syscall() for protected mode that
can handle all of MS-DOS.

We start by looking at the C function int86x(), which takes an interrupt
number and a struct holding register values and does the interrupt. If
let off from real (or VM86) mode, this will do for a lot of things, but
not for any of the calls that take pointers to parameter blocks in
registers. So, we need a way of passing such blocks down into the low
1Mb. I think it goes like this:

       syscall(        SCALAR,         # Interrupt number
                       SCALAR,         # Class - see below
                       SCALAR,         # Registers - see below
                       LIST            # Depend on Class
               ) ;

This version of syscall returns success or failure values thus:

If the carry flag was set on return (indicates an error on most calls),
return the undefined value. Otherwise, return the value of AX, returning
zero as "0 but true". On an error, $! is set.

The interrupt number is just that. The registers scalar holds a packed
set of values to be loaded into registers, thus:

 pack( "SSSSSSSSS", $AX, $BX, $CD, $DX, $SI, $DI, $BP,
     $DS, $ES) ;
This block is copied into low memory and loaded into the registers
before the real-mode interrupt is let off. When it returns, the new
register values are packed back into the low-memory block and then copied
back into the scalar's string buffer in high memory.

The class value is used to control the interpretation of the parameter
LIST. It is the bit-wise OR of the following values. Each set bit in
class eats one value from the LIST.

1: A filehandle is used: the corresponding element of the LIST is a Perl
filehandle, which is reduced to an MS-DOS filehandle and placed in BX
before the interrupt is executed, overwriting the value passed in the
registers scalar.  One of the few standardised parts of the MS-DOS API is
that file handles are always passed in BX; the only exceptions are the
dup() calls, which Perl supplies an interface for anyway.
       Warning: mixing handle i/o calls and Perl i/o on the same
file will produce an unholy mess. This facility is intended for
doing IOCTL calls on filehandles, not for moving data.

2: The call uses a buffer whose address is passed in DS:DX. The contents
of the corresponding SCALAR are copied into a low-memory buffer whose
address is placed in DS:DX (overwriting the value from the registers
scalar) before the interrupt is executed.  This low-memory buffer can be
a maximum of 512 bytes; no normal call uses more. Users should be warned
that trying to use will probably crash the system. Once the interrupt
returns, the contents of the buffer that DS:DX *now* points to are copied
back into the SCALAR. The user is responsible for making sure that the
SCALAR's string value is big enough to hold any returned data block: its
size should be noted by the syscall() handler and used to control the
amount of data copied back from low memory.

4: As above, but for a DS:BX buffer. This is used by some INT21 calls.
(INT25 and INT26 also use it, but leave an extra copy of the CPU flags on
the stack. A really helpful syscall()
implementation would remmember to pop the extra copy of the flags off the
stack before trying to return, thus avoiding a crash. However, Perl
programs probably don't have any good reason for doing raw disk sector
I/O and INT25 and INT26 have been obsoleted by others that don't share
this problem.)

MS-DOS Perl syscall() support should allow for 2 buffers to be used in
the same call; don't make any effort to preserve the contents of the
low-memory buffers between syscall() invocations.

8: As above, for DS:SI.

16: As above, for ES:BX

32: As above, for ES:DI

64: As above, for ES:BP (used by BIOS)

This pointer meachanism does not support *all* DOS calls, but the ones
excluded are highly obscure. They include the DOSSHELL task swapper,
which uses DX:DC and BX:CX pointers, plus INT2F/AE0x and INT2F/B804 which
use pairs of DS-based pointers.

Fictional example: SmashScreen is called via INT23h. with AH=14h and a
subfunction code in AL [0=enquire screen existence, 1=enquire if screen
is already smashed, 2=select colour/mono screen for other operations,
3=reserved, 4=Smash screen, 5=repair screen]. DS:DX points to a parameter
block giving the pattern to smash the glass with, ES:DI points to an
undocumented block used for energy-saving controls and BX holds the
handle of an already open file to sweep the bits of glass into.

# Include the support values for SYSCALL. These define $AX, # $BX, etc as
zero.

require "syscall.ph" ;

# Set up register values for a screen-smash

open( TRASHFILE, ">\temp\splinters") ||         die "Couldn't open file:
$!\n" ;
       $SmashAPI = hex( "23") ;

$AX = hex("1404") ;

$regs = pack( "SSSSSSSSS", $AX, $BX, $CD, $DX, $SI, $DI, $BP,
           $DS, $ES) ;

$smashpattern = "+++++++++" ;
$energy_save = "Snooze" ;

$class =        1 +             # 1 => Filehandle used
               2 +             # 2 => DS:DX buffer used
               32 ;            # 32 => ES:DI buffer used
               $success = syscall( $SmashAPI, $class, $regs,
           TRASHFILE, $smashpattern, $energy_save) ;

Syscall has to interpret $class so that it knows how to deal with the
parameters, and how to pass them back.

Clearly, it will be very easy to crash the system by making use of this
interface, but it is also possible to manage all of the assorted DOS
enquiry/toggle functions (disk verify, raw/cooked terminal I/O, etc)
quite easily.

John Dallman, [email protected] - still using MS-DOS Perl and
enjoying it.