>Newsgroups: comp.lang.perl
>From: jgd@cix.compulink.co.uk ("John Dallman")
>Subject: Re: Perl for DOS needs int86x()  (was Serial I/O & port setup :DOS:
>Message-ID: <CwE6Hv.8Mw@cix.compulink.co.uk>
>Organization: Compulink Information eXchange
>References: <Cw7qIv.Jq8@srgenprp.sr.hp.com>
>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 darrylo@sr.hp.com 
(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, jgd@cix.compulink.co.uk - still using MS-DOS Perl and 
enjoying it.