Dec 132017
 
Document describing use of 0x2F multiplex interrupt.
File MUX.ZIP from The Programmer’s Corner in
Category Tutorials + Patches
Document describing use of 0x2F multiplex interrupt.
File Name File Size Zip Size Zip Type
MUX.TXT 28769 9016 deflated

Download File MUX.ZIP Here

Contents of the MUX.TXT file


---------------------------------
Using the DOS Multiplex Interrupt
for TSR Communications

by Chris Dunford
The Cove Software Group

April 22, 1993
---------------------------------


This document discusses the use of the multiplex interrupt (2Fh) for
communications by DOS TSRs. It is intended for programmers who are
writing TSRs and/or applications that must communicate with TSRs.
Knowledge of assembler language is assumed.

I wrote this document some time ago for internal use, but a recent spate
of inquiries about TSR communications in the IBM Programming forum on
CompuServe has encouraged me to clean it up a bit, add the introductory
material, and post it for all to use. I hope this information proves
valuable to a few programmers.


Background
----------
Beginning with the first TSRs, there has been a need for communications
between installed copies of TSRs and transient programs. For example:

- When a TSR is executed, it is useful for it to know whether or not
it is already present in memory. Without this knowledge, it's easy
to accidentally load a TSR twice.

- A TSR may be designed so that the user can re-run the TSR load
module with new options that need to be communicated to the already
resident copy.

- A TSR may be designed to provide services to other applications or
to be controllable by other applications. For example, a screen
saver might provide an interface by which applications could
disable and enable screen blanking when necessary.

Many techniques have been used for locating and communicating with the
resident copy of a TSR. Among them have been:

- Searching through memory or following the memory block chain for a
known signature.

- Looking at the area of memory addressed by a hooked interrupt.

- Adding a "private" function to a common interrupt, e.g.,
responding to int 21h, function FFh.

- Using a dedicated software interrupt.

- Writing the TSR as a device driver and addressing it via DOS I/O
calls.

All of these methods have serious drawbacks. Searching through memory
is relatively slow and not very reliable; looking at hooked interrupts
only works if your TSR was the last to hook; private functions and
dedicated interrupts invite software conflicts. Casting the TSR as a
device is the best solution in terms of reliability; however, forcing a
TSR to be a device driver is inelegant (unless the TSR really provides
device-like services), makes it difficult to unload the TSR, and limits
the user's flexibility in load ordering.

This document describes a different way to communicate with a TSR, using
the multiplex interrupt (int 2Fh). This method is easy to use,
eliminates most conflicts, and, barring such conflicts, never fails to
find an already-installed and never finds a TSR to be loaded when it
isn't.

Is the method foolproof? No. See the last section of this document for
possible problems and a brief description of one alternative that has
been suggested.


The multiplex interrupt
-----------------------
Interrupt 2Fh is the multiplex (or "mux") interrupt. It has many uses.
To name just a few, DOS's PRINT command has used it for internal
communications and to provide printing services since DOS 2.0; Windows
and DOSSHELL use it to broadcast certain messages to interested
applications; DOSKEY uses it to detect itself when reloaded and to
provide input editing services.

The key to the mux interrupt is the mux ID code. This code is placed in
the AH register before performing an int 2Fh call, and, in general, it
identifies the program whose services are being requested. For example,
DOSKEY's mux ID code is 48h. To detect DOSKEY or to request an edited
command from DOSKEY, an application places the ID code 48h in AH and a
function code in AL and then executes an int 2Fh.

When an interrupt 2Fh is executed, each application that has hooked the
interrupt examines the ID code in AH. If it is the application's ID
code, the application performs the requested service and IRETs;
otherwise, it chains to the previous mux handler. For example, DOSKEY's
mux handler might look like this:

MuxHandler PROC FAR

cmp ah, 48h
je DOSKEYService
jmp PrevMuxHandler

DOSKEYService:
...perform service requested by function code in AL
iret

MuxHandler ENDP

You can use the mux interrupt under any DOS version except 1.x.


Mux ID codes
------------
Obviously, there are 256 possible mux ID codes (00h - FFh). DOS
reserves the first 192 codes (00h - BFh) for its own use; the remaining
64 codes (C0h - FFh) are available for use by applications.

DOS mux handlers use dedicated ID codes: DOSKEY's ID code is always
48h, PRINT's ID code is always 01h, and HIMEM.SYS's ID code is always
43h. Microsoft can do this, but you can't. If you pick a specific ID
code for your TSR, some other programmer is guaranteed to pick the same
code, and your applications will conflict. You might as well use a
dedicated interrupt.

The solution is to not use a dedicated code; rather, you find an unused
ID code at load time and stake a claim. Each time you load, you could
be using a different code.

So, if you pick a new code each time you run, how do other applications
(or reloads of your own TSR) know which code is in use? As one
programmer wrote on CompuServe, "isn't this rather like changing your
telephone number every day and not telling your friends?" Well, yes and
no. You are changing your phone number every day, but there are only 64
possible phone numbers; your friends can call all of them in a few
microseconds--and they're guaranteed not to disturb anyone's dinner
when they call the wrong numbers because everyone has an answering
machine.

The key is that all user mux handlers are expected to respond uniformly
to one specific function, namely, the "installation check" function
(AL = 0).


Installation check
------------------
It's very, very simple. Any mux handler, when it sees an int 2FH with
its own ID code in AH and AL = 0, changes the value in AL before it
IRETs. That's the only rule.

Now, suppose you're an application that is looking for a TSR. You can
very easily tell whether or not a specific ID code is in use:

mov ah, 0C0H ; for example
xor al, al ; Set AL to 0
xor bx, bx ; Also BX, CX, DX
xor cx, cx
xor dx, dx
int 2Fh

(Some writers, such as Ralf Brown, have recommended that BX, CX, and DX
should be set to zero when this test is made, and I adhere to that
recommendation.)

On return, if AL contains anything other than zero, the ID code is in
use. An application can find all unused ID codes by simply looping
through all possible user codes (C0h - FFh). In pseudocode:


DO FOR Code = C0h TO FFh
set AH = Code
set AL,BX,CX,DX = 0
perform int 2Fh
IF AL <> 0 THEN Code is in use /* but by who? */
END DO

OK so far, but you saw the question. How does the application know that
a code that is "in use" is being used by the TSR it's looking for?


Identifying your TSR
--------------------
The answer is that in responding to the installation check call, each
TSR will return some additional application-dependent information that
identifies itself. There are no hard-and-fast standards for how this is
done; the intent of the standard is not that you can identify ALL
installed mux handlers, just that you can find the one you're looking
for.

Probably the most common technique is this: when responding to function
0, the TSR will set a CPU register pair to point to an identification
string. I usually use ES:DI for this, but you can use any registers you
want. Here is typical skeleton code for the entry and installation
check functions of a mux handler:

(data)
MyIDCode DB ? ; Assigned at load time
IdentString DB "MyLittleTSR"

(code)
MuxHandler PROC NEAR

; See if the call's for us.
cmp ah, MyIDCode ; Request for me?
je ServiceMux ; Yes, go to service dispatcher.
jmp PrevMuxHandler ; No, chain to next handler.

; Call is for us. See what function is requested.
ServiceMux:
cmp al, 0 ; Test installed state?
je ReturnInstalledState ; Yes, go to that routine.

...handle other functions here
...

jmp MuxHandlerExit

; Code to handle function 0, test installed state. Just
; return AL = FF and ES:DI -> ident string.
ReturnInstalledState:
push cs
pop es ; Set ES = our segment
mov di, OFFSET IdentStr ; Now ES:DI -> our ident string
mov al, 0FFh ; And return AL <> 0

; Common exit from mux handler if it was our call.
MuxHandlerExit:
iret

MuxHandler ENDP

In order for any application to determine whether or not you are loaded,
it simply has to do this:

DO FOR Code = C0h TO FFh
set AH = Code
set AL,BX,CX,DX = 0
perform int 2Fh
IF AL = FFH THEN
IF string at ES:DI = "MyLittleTSR" then
the TSR is loaded and is using mux ident code 'Code'
END IF
END IF
END DO

Of course, if you want third-party applications to be able to detect
your TSR, you will need to document your identification technique.


Other mux functions
-------------------
Other than for function 0 (installation check, as described above),
there are no particular rules for what other mux functions are supposed
to do. You are creating the mux handler and the services it provides,
so you can do whatever you want.

For upward compatibility, I like to set AL=0 before returning from a
supported function (other than function 0); if a call is made with an
unknown function code, I return AL unchanged. In this way an
application that calls the mux handler can easily determine whether or
not the function it requested is supported by the version of the TSR
that is currently loaded:

...
mov ah, ...
int 2Fh
or al,al
jnz FunctionNotSupported

However, this is just my personal usage; it's not a requirement.


Code: TSR
---------
The following program skeleton shows how a mux-aware TSR might look.
For simplicity, we'll assume a COM format program. The example mux
handler implements one additional function: function 1 returns the
version number of the installed copy in BX.

The key to this code is the function MuxSearch. This is a generic
function, completely coded, that you can use in your own programs; it
searches for the installed copy of a TSR. See the function prologue for
full information.

IMPORTANT: mux handlers are permitted to change ANY registers when
responding to a function 0 call. Our mux handler changes ES:DI, but
other handlers could change any registers they want. When you perform
an installation test, you must save ALL registers that are important to
you (except, of course, SS:SP).

PLEASE NOTE: the skeleton program is BASED ON but not COPIED FROM
working code in my own software. In other words, this is bench code and
may contain bugs. The MuxSearch function is, however, copied from
working code, and I believe it to be bugfree.

Code SEGMENT
ASSUME CS:Code

VERSION EQU 123

; ***********************************************************
; RESIDENT CODE & DATA
; ***********************************************************

ORG 100h
Entry: jmp Start

; -----------------------------------------------------------
; Resident data goes here: whatever you need, plus a byte
; called MuxIDCode, the TSR identification string, and the
; saved address of the previous mux handler.
; -----------------------------------------------------------

MuxIDCode DB 0

IdentStr DB 'MyTSRIdentString'
IDENTSTRLEN EQU $ - IdentStr

PrevMuxHandler DD ?

... other data here as needed by your TSR


; -----------------------------------------------------------
; Resident code goes here: MUX handler plus whatever else
; you need.
; -----------------------------------------------------------

; ----- MuxHandler ------------------------------------------
; The mux handler routine as we've shown previously, plus
; code for our function 1 (return version in BX).
;
MuxHandler PROC FAR
ASSUME ds:NOTHING, es:NOTHING, ss:NOTHING

; Test for our ID code; chain to previous handler
; if it isn't ours.
cmp ah, MuxIDCode
je MuxDispatch
jmp PrevMuxHandler

; The call is for us. Dispatch to service routines based on
; function code in AL.
MuxDispatch:
or al, al
jz ReturnInstalledState

cmp al, 1
je ReturnVersion

... Code to test for other supported functions here.

... If function requested is not supported, just return
... AX unchanged so that the caller knows it didn't work.
jmp MuxExit

; Service code for function 0: installation check. Return
; AL = FF and ES:DI -> the ident string.
ReturnInstalledState:
push cs
pop es
mov di, OFFSET MuxIdentStr
mov al, 0FFh
jmp MuxExit

; Service code for function 1: get version. Return AL = 0 and
; BX = version number
ReturnVersion:
mov bx, VERSION
xor al, al
jmp MuxExit

... Code for other mux functions here

; Common exit back to mux caller. Not used if the mux call
; wasn't for us (we chained to the previous mux handler).
MuxExit:
iret

MuxHandler ENDP

; ...other resident code here as needed


; ***********************************************************
; TRANSIENT CODE & DATA
; ***********************************************************

ASSUME ds:code, es:code, ss:code

; -----------------------------------------------------------
; The entry point. We call MuxSearch to find out if we're
; already loaded and go from there. MuxSearch returns
; AL = 1 if we're already loaded, else 0. If the TSR is
; already loaded (AL=1), AH contains the mux ID code it's using.
; If the TSR is not loaded (AL=0), AH contains an unused mux ID
; that we can use.
;
; Either way, we need the ID code, so we save it.
;
; The call to MuxSearch requires a pointer to the ident string in
; DS:SI and the length of the string in CX.
; -----------------------------------------------------------
Start:
mov cx, IDENTSTRLEN
mov si, OFFSET IdentStr
call MuxSearch
mov MuxIDCode, ah
or al, al ; Already loaded?
jnz Reload ; Yes!

; -----------------------------------------------------------
; If we're here, we're not already loaded; we can load
; ourselves. We've already put the unused mux ident code
; in MuxIDCode, and that will be the one we'll respond
; to after we're loaded.
; -----------------------------------------------------------

; Test for the EXTREMELY unlikely case of all mux codes
; already in use (MuxIDCode = 0).
cmp MuxIDCode, 0
jne Install

; If we're here, there are no free mux codes
... display warning message and terminate.

Install:
; Mux is OK, save current mux handler address
push es
mov ax, 352Fh
int 21h
mov WORD PTR PrevMuxHandler, bx
mov WORD PTR PrevMuxHandler + 2, es
pop es

; Install new mux handler
mov ax, 252Fh
mov dx, OFFSET MuxHandler
int 21h

... That's it as far as mux is concerned
... Rest of installation code goes here.

; Exit, remaining resident.
mov dx, ... ; Size of TSR
mov ah, 31h
int 21h

; -----------------------------------------------------------
; If we get here, the TSR is already loaded. We can
; communicate with it using the mux ID code that we earlier
; left in MuxIDCode. As an example, we test that the version
; of the loaded copy matches the version of the reloaded copy
; (in case the user has multiple versions on disk).
; -----------------------------------------------------------
Reload:
; Check version (function 1). The handler returns
; AL = 0 and BX = version. If AL doesn't contain
; 0 on return (indicating that the function isn't
; supported) or BX has the wrong version, we cancel.
mov ah, MuxIDCode
mov al, 1
int 2Fh
or al, al
jnz VersionMismatch
cmp bx, VERSION
je VersionOK

VersionMismatch:
; There's a version mismatch!
... display warning message
jmp ReloadExit

VersionOK:
; Versions match.
... continue whatever transient processing is needed

; Common exit if we're not remaining resident
ReloadExit:
mov ah, 4Ch
int 21h


; ----- MuxSearch ----------------------------------------------
; This generic function searches for a TSR. The function assumes
; COM format.
;
; Basically, we just loop through all ident codes (C0 - FF) until
; we either find ourselves or run out of codes.
;
; Entry:
; DS:SI Contains a pointer to the TSR identification string
; CX Contains the length of the string
;
; Exit:
; AL Contains found/not found code
; 0: TSR is not currently loaded
; 1: TSR is already loaded
;
; AH Contains a mux ident code
; If AL=0, this is an unused mux code that we can use. If
; AH=0, then all mux codes are in use (unlikely!).
; If AL=1, this is the mux code that the loaded copy is using.
;
; All other registers preserved.
;
; Assumptions:
; None. This function will work in either EXE or COM
; format programs.
; --------------------------------------------------------------

MuxSearch PROC NEAR
ASSUME ds:NOTHING, es:NOTHING, ss:NOTHING

; Save ALL registers except AX. This is needed because the
; mux handlers are allowed to change ANY registers.
push bx
push cx
push dx
push si
push di
push bp
push ds
push es

; We'll maintain mux code information in BX. BL will contain
; the lowest numbered free code and BH will contain the code
; that we're currently testing.
mov bx, 0C000h

; We begin the loop here. On each iteration of the loop, we
; test the mux code currently in BH. The loop terminates in two ways:
; if we find an already loaded copy (exit from center of loop),
; or if the current test code wraps from 0FFh to 00h (exit
; from end of loop).
SearchLoop:

; Save our key registers (BX, CX, SI, and DS) before
; calling mux for the presence test; perform the test
; for the code in BH; and recover the registers.
push bx
push cx
push si
push ds

mov ah, bh ; AH = current mux code
xor al, al ; AL = 0 for presence test
xor bx, bx ; Also BX=CX=DX=0
xor cx, cx
xor dx, dx
int 2Fh

pop ds
pop si
pop cx
pop bx

; If AL is nonzero, the code is in use by someone.
or al, al
jnz IsItUs

; Mux code BH is not in use. If this is the first free
; code we've found (BL = 0), save the code. In any
; event, go to the next loop iteration.
or bl, bl
jnz SearchNext
mov bl, bh
jmp SearchNext

; Mux code BH is in use--is it us? If it is, the
; string at ES:DI will match DS:SI for length CX.
IsItUs:
push cx
push si
repe cmpsb
pop si
pop cx

; If the zero flag is clear (NZ), the strings didn't
; match and we can go to the next iteration.
jnz SearchNext

; We've found ourselves, using mux code BH. We want
; to return AH = the code and AL = 1.
mov ah, bh
mov al, 1
jmp MuxSearchExit

; This is the iteration-end code. If we're here, we
; haven't found ourselves yet. Increment the code (BH)
; and loop back if it didn't wrap past FF to 00.
SearchNext:
inc bh
jnz SearchLoop

; If we get here, we fell out of the loop without finding a loaded
; copy of ourselves. We'll return AL = 0 and AH = the lowest
; numbered free code (BL). This will be zero in the extremely
; unlikely event that all mux codes are in use.
mov ah, bl
xor al, al

; Common exit. At this point, AH and AL should be set as described
; in the prologue.
MuxSearchExit:
pop es
pop ds
pop bp
pop di
pop si
pop dx
pop cx
pop bx
ret
MuxSearch ENDP

Code ENDS
END Entry


NOTE: for simplicity, the code shown above for the mux intercept does
not follow IBM's interrupt sharing protocol (which allows interrupts to
be unhooked out of sequence). I do strongly recomment that you follow
this protocol.


Application code
----------------
The above code demonstrated how to write the TSR. But what about other
applications that want to access the TSR?

It really works the same way; external applications can still use
MuxSearch. The only difference is in what the application does if the
TSR isn't loaded (it will display a message or take other appropriate
action rather than loading itself as the TSR does). Sample code:

(data)
MuxIDCode DB 0

IdentStr DB 'MyTSRIdentString'
IDENTSTRLEN EQU $ - IdentStr

(code)
mov si, OFFSET IdentStr
mov cx, IDENTSTRLEN
call MuxSearch
or al, al
jnz L1

; If here, the TSR is not installed. Perhaps, complain to the
; user and terminate.
...display "TSR is not installed, please install it"
...terminate

; If here, the TSR is installed and is using mux code AH. Just
; save the mux code and continue working. We can talk to the
; TSR using MuxIDCode.
L1:
mov MuxIDCode, ah
...


Possible problems
-----------------
The protocol described above has been written about in various places,
but never, to my knowledge, on stone tablets. In other words, TSR
writers who use the multiplex interrupt aren't required by anyone to
follow the protocol. Thus, the possibility of conflicts exists.

There are two obvious potential problems, which I will cover briefly
below. I don't know how many TSRs are in distribution that fall into
these "bad guy" categories. I can say, however, that I have at least
two TSRs in fairly wide circulation (one free and one commercial) that
use this protocol. I have had no reports as of 4/22/93 of any problems.

The problems:


PROBLEM: a TSR uses a mux code from C0-FF but returns 0 in AL in
response to the presence test (AH=0).

RESULT: a conforming TSR testing this mux code would be led to believe
that the code is not in use. It could therefore install itself "over"
the nonconforming TSR that is already using the code. The problem is
somewhat mitigated by the fact that, if you use the code shown above,
there would have to be no truly-free mux codes with lower numbers. In
other words, if the "bad" TSR is using code C2 but code C1 is free,
you'd use C1 and no conflict would occur.

As a partial guard against this problem, I recommend testing BX, CX, and
DX for nonzero values on return from the presence test (on the chance
that the TSR might change BX, CX, or DX even if it doesn't change AL).
If any are found to be nonzero, assume that the code is in use even if
AL returns 0.


PROBLEM: without proper testing, a TSR blindly uses the mux code that
you're already using.

RESULT: your TSR is disabled, or worse.

There's no obvious solution to this problem other than to complain to
the vendor of the nonconforming TSR. If the TSR responds in some way to
your presence test, the user might be able to solve the problem by
loading the nonconforming TSR first.


An alternative proposal
-----------------------
Ralf Brown, principal compiler of the famous PC "Interrupt List" and
co-author of "PC Interrupts" has suggested an alternative protocol for
TSR communications.

The Brown protocol, which is called AMIS (Alternative Multiplex
Interrupt Specification), is basically an extension of the mux protocol
described here. AMIS uses interrupt 2Dh rather than 2Fh; int 2Dh is
reserved by DOS but currently unused.

AMIS uses the same basic technique for detecting installed TSRs and
identifying unused codes, but is more specific as to register usage and
signature string format. It also specifies a set of services to be
provided in response to certain function codes other than function 0;
for example, function 1 returns an API entry point (if the application
supports direct calls), and function 2 is an "uninstall" call. (It is
not required that TSRs support all of the defined calls.)

If you are interested in the full specification, refer to "PC
Interrupts" or obtain a copy of the Interrupt List, which is distributed
free of charge via Internet, CompuServe (IBMPRO forum), and many other
distribution points.



DISCLAIMER
----------

This document is being distributed as a service to other programmers and
contains information that is correct to the best of my knowledge. There
may be errors. I am not responsible for any problems, perceived or
real, that may result from your use of the information contained herein.

Please try to let me know if you find any errors, and I will endeavor to
distribute a corrected copy.

---------------------

by:
Chris Dunford
The Cove Software Group
PO Box 1072
Columbia, MD 21044

Tel: 410/992-9371
CompuServe: 76703,2002
Internet: [email protected]

Uncopyrighted material
Please distribute freely



 December 13, 2017  Add comments

Leave a Reply